diff --git a/python/smc/control/clik/clik.py b/python/smc/control/clik/clik.py index e8e9a5810a80fbabaaa657917365a30b3abc166a..a65d2ed072c002ee1fc48390a465f9dd10569191 100644 --- a/python/smc/control/clik/clik.py +++ b/python/smc/control/clik/clik.py @@ -1,5 +1,5 @@ from smc.control.control_loop_manager import ControlLoopManager -from smc.robots.robotmanager_abstract import RobotManagerAbstract +from smc.robots.robotmanager_abstract import AbstractRobotManager from smc.multiprocessing.process_manager import ProcessManager import pinocchio as pin @@ -358,7 +358,7 @@ def getClikController(args, robot): return partial(dampedPseudoinverse, args.tikhonov_damp) -def controlLoopClik(robot: RobotManagerAbstract, clik_controller, i, past_data): +def controlLoopClik(robot: AbstractRobotManager, clik_controller, i, past_data): """ controlLoopClik --------------- @@ -396,7 +396,7 @@ def controlLoopClik(robot: RobotManagerAbstract, clik_controller, i, past_data): def controlLoopClikDualArm( - robot: RobotManagerAbstract, clik_controller, goal_transform, i, past_data + robot: AbstractRobotManager, clik_controller, goal_transform, i, past_data ): """ controlLoopClikDualArm @@ -499,7 +499,7 @@ def controlLoopClikArmOnly(robot, clik_controller, i, past_data): def moveUntilContactControlLoop( - args, robot: RobotManagerAbstract, speed, clik_controller, i, past_data + args, robot: AbstractRobotManager, speed, clik_controller, i, past_data ): """ moveUntilContactControlLoop @@ -561,7 +561,7 @@ def moveUntilContact(args, robot, speed): print("Collision detected!!") -def moveL(args, robot: RobotManagerAbstract, goal_point): +def moveL(args, robot: AbstractRobotManager, goal_point): """ moveL ----- @@ -590,7 +590,7 @@ def moveL(args, robot: RobotManagerAbstract, goal_point): def moveLDualArm( - args, robot: RobotManagerAbstract, goal_point, goal_transform, run=True + args, robot: AbstractRobotManager, goal_point, goal_transform, run=True ): """ moveL @@ -635,7 +635,7 @@ def moveLFollowingLine(args, robot, goal_point): def cartesianPathFollowingWithPlannerControlLoop( - args, robot: RobotManagerAbstract, path_planner: ProcessManager, i, past_data + args, robot: AbstractRobotManager, path_planner: ProcessManager, i, past_data ): """ cartesianPathFollowingWithPlanner @@ -698,7 +698,7 @@ def cartesianPathFollowingWithPlannerControlLoop( def cartesianPathFollowingWithPlanner( - args, robot: RobotManagerAbstract, path_planner: ProcessManager + args, robot: AbstractRobotManager, path_planner: ProcessManager ): controlLoop = partial( cartesianPathFollowingWithPlannerControlLoop, args, robot, path_planner @@ -711,7 +711,7 @@ def cartesianPathFollowingWithPlanner( loop_manager.run() -def controlLoopCompliantClik(args, robot: RobotManagerAbstract, i, past_data): +def controlLoopCompliantClik(args, robot: AbstractRobotManager, i, past_data): """ controlLoopClik --------------- @@ -835,7 +835,7 @@ def clikCartesianPathIntoJointPath( sim_args.visualizer = False sim_args.save_log = False # we're not using sim robot outside of this sim_args.max_iterations = 10000 # more than enough - sim_robot = RobotManagerAbstract(sim_args) + sim_robot = AbstractRobotManager(sim_args) sim_robot.q = q_init.copy() sim_robot._step() for pose in path: @@ -870,7 +870,7 @@ def clikCartesianPathIntoJointPath( def controlLoopClikDualArmsOnly( - robot: RobotManagerAbstract, clik_controller, goal_transform, i, past_data + robot: AbstractRobotManager, clik_controller, goal_transform, i, past_data ): """ controlLoopClikDualArmsOnly @@ -918,7 +918,7 @@ def controlLoopClikDualArmsOnly( def moveLDualArmsOnly( - args, robot: RobotManagerAbstract, goal_point, goal_transform, run=True + args, robot: AbstractRobotManager, goal_point, goal_transform, run=True ): """ moveL @@ -948,7 +948,7 @@ def moveLDualArmsOnly( if __name__ == "__main__": args = get_args() - robot = RobotManagerAbstract(args) + robot = AbstractRobotManager(args) Mgoal = robot.defineGoalPointCLI() clik_controller = getClikController(args, robot) controlLoop = partial(controlLoopClik, robot, clik_controller) diff --git a/python/smc/multiprocessing/networking/TODO b/python/smc/multiprocessing/networking/TODO new file mode 100644 index 0000000000000000000000000000000000000000..9e843d64cb62df340ae9ec61d72e2057a7d6bcf7 --- /dev/null +++ b/python/smc/multiprocessing/networking/TODO @@ -0,0 +1 @@ +rename receiver and sender to subscriber and publisher diff --git a/python/smc/robots/get_model.py b/python/smc/robots/get_model.py index 87a285da828a530f9e36aa9bca8fb850b59d15a3..e416db92db9431842d2bbd070056285fe6365d43 100644 --- a/python/smc/robots/get_model.py +++ b/python/smc/robots/get_model.py @@ -43,9 +43,8 @@ def get_model(): ) urdf_path_absolute = os.path.abspath(urdf_path_relative) mesh_dir = files("smc") - mesh_dir = mesh_dir.joinpath("robots/robot_descriptions") + mesh_dir = mesh_dir.joinpath("robots") mesh_dir_absolute = os.path.abspath(mesh_dir) - print(mesh_dir_absolute) shoulder_trans = np.array([0, 0, 0.1625134425523304]) shoulder_rpy = np.array([-0, 0, 5.315711138647629e-08]) @@ -202,7 +201,7 @@ def get_heron_model(): def get_yumi_model(): - urdf_path_relative = files("smc.robot_descriptions").joinpath("yumi.urdf") + urdf_path_relative = files("smc.robots.robot_descriptions").joinpath("yumi.urdf") urdf_path_absolute = os.path.abspath(urdf_path_relative) # mesh_dir = files('smc') # mesh_dir_absolute = os.path.abspath(mesh_dir) diff --git a/python/smc/robots/interfaces/dual_arm_interface.py b/python/smc/robots/interfaces/dual_arm_interface.py index 8d3a04e0e7ceb8eac31b12db17bf27424dee8897..e9b0d605394cf669a3aa2c03387b4012e159f6e7 100644 --- a/python/smc/robots/interfaces/dual_arm_interface.py +++ b/python/smc/robots/interfaces/dual_arm_interface.py @@ -1,11 +1,11 @@ -from smc.robots.robotmanager_abstract import RobotManagerAbstract +from smc.robots.robotmanager_abstract import AbstractRobotManager import abc import numpy as np import pinocchio as pin -class DualArmInterface(RobotManagerAbstract): +class DualArmInterface(AbstractRobotManager): """ DualArmInterface ---------------- @@ -14,42 +14,58 @@ class DualArmInterface(RobotManagerAbstract): - r stands for right """ + # NOTE: you need to fill in the specific names from the urdf here for your robot + # (better than putting in a magic number) + # self._l_ee_frame_id = self.model.getFrameId("left_ee_name") + # self._r_ee_frame_id = self.model.getFrameId("right_ee_name") + @property @abc.abstractmethod - def __init__(self): - self._abs_frame: pin.SE3 - # NOTE: you need to fill in the specific names from the urdf here for your robot - # self._l_ee_frame_id = self.model.getFrameId("left_ee_name") - # self._r_ee_frame_id = self.model.getFrameId("right_ee_name") + def l_ee_frame_id(self) -> int: ... + @property + @abc.abstractmethod + def r_ee_frame_id(self) -> int: ... - self._l_ee_frame_id: int - self._r_ee_frame_id: int + # T_abs_l and T_abs_r are relative transformations between the absolute dual-arm frame, + # and the left and right end-effector frames respectively + @property + @abc.abstractmethod + def T_abs_r(self) -> pin.SE3: ... + @property + @abc.abstractmethod + def T_abs_l(self) -> pin.SE3: ... - self._abs_T_w_e: pin.SE3 - self._l_T_w_e: pin.SE3 - self._r_T_w_e: pin.SE3 + def __init__(self): + self._T_w_abs: pin.SE3 + self._T_w_l: pin.SE3 + self._T_w_r: pin.SE3 # TODO: you have to have relative frame transformations here # (from absolute frame to left frame and to the right frame) raise NotImplementedError - def getAbsT_w_e(self, q=None): + def constructT_w_abs(self): + raise NotImplementedError + + @property + def T_w_abs(self, q=None): """ - getAbsT_w_e + getT_w_abs ----------- get absolute frame, as seen in base frame, based on the current, or provident joint configuration TODO: figure out what's the best way to get this """ - raise NotImplementedError + raise NotImplementedError("need to add the case where you construct it from q") + return self._T_w_abs.copy() def getLeftRightT_w_e(self, q=None): """ getLeftRightT_w_e ----------- - returns a tuple (l_T_w_e, r_T_w_e), i.e. left and right end-effector frames in the base frame, + returns a tuple (T_w_l, T_w_r), i.e. left and right end-effector frames in the base frame, based on the current, or provident joint configuration """ if q is None: - return (self._l_T_w_e.copy(), self._r_T_w_e.copy()) + return (self._T_w_l.copy(), self._T_w_r.copy()) assert type(q) is np.ndarray # NOTE: # calling forward kinematics and updateFramePlacements here is ok @@ -64,17 +80,17 @@ class DualArmInterface(RobotManagerAbstract): ) # NOTE: this also returns the frame, so less copying possible # pin.updateFramePlacements(self.model, self.data) - pin.updateFramePlacement(self.model, self.data, self._l_ee_frame_id) - pin.updateFramePlacement(self.model, self.data, self._r_ee_frame_id) + pin.updateFramePlacement(self.model, self.data, self.l_ee_frame_id) + pin.updateFramePlacement(self.model, self.data, self.r_ee_frame_id) return ( - self.data.oMf[self._l_ee_frame_id].copy(), - self.data.oMf[self._r_ee_frame_id].copy(), + self.data.oMf[self.l_ee_frame_id].copy(), + self.data.oMf[self.r_ee_frame_id].copy(), ) def forwardKinematics(self): - pin.updateFramePlacement(self.model, self.data, self._l_ee_frame_id) - pin.updateFramePlacement(self.model, self.data, self._r_ee_frame_id) - self._l_T_w_e = self.data.oMf[self._l_ee_frame_id].copy() - self._r_T_w_e = self.data.oMf[self._r_ee_frame_id].copy() - self.getAbsT_w_e() + pin.updateFramePlacement(self.model, self.data, self.l_ee_frame_id) + pin.updateFramePlacement(self.model, self.data, self.r_ee_frame_id) + self._T_w_l = self.data.oMf[self.l_ee_frame_id].copy() + self._T_w_r = self.data.oMf[self.r_ee_frame_id].copy() + self.constructT_w_abs() raise NotImplementedError diff --git a/python/smc/robots/interfaces/force_torque_sensor_interface.py b/python/smc/robots/interfaces/force_torque_sensor_interface.py index 89f1454c08f61b605fe9d178264966e1e9d7f7a0..268e9485abf8fa3f7a9c223e880bcb8d87df2693 100644 --- a/python/smc/robots/interfaces/force_torque_sensor_interface.py +++ b/python/smc/robots/interfaces/force_torque_sensor_interface.py @@ -11,12 +11,13 @@ class ForceTorqueSensorInterface(abc.ABC): # NOTE: wrench bias will be defined in the frame your sensor's gives readings self._wrench_bias = np.zeros(6) + # get this via model.getFrameId("frame_name") @property - def _ft_sensor_frame_id(self): - # whatever you set to be the frame - ... + @abc.abstractmethod + def ft_sensor_frame_id(self) -> int: ... - def getWrench(self): + @property + def wrench(self): """ getWrench --------- @@ -26,7 +27,8 @@ class ForceTorqueSensorInterface(abc.ABC): """ return self._wrench.copy() - def getWrenchBase(self): + @property + def wrench_base(self): """ getWrench --------- @@ -36,7 +38,7 @@ class ForceTorqueSensorInterface(abc.ABC): return self._wrench_base.copy() @abc.abstractmethod - def _getWrench(self): + def getWrenchFromReal(self): """ get wrench reading from whatever interface you have. NOTE: @@ -52,10 +54,8 @@ class ForceTorqueSensorInterface(abc.ABC): """ pass - def _getWrenchInEE(self, step_called=False): - if self.robot_name != "yumi": - else: - self.wrench = np.zeros(6) + def _getWrenchInEE(self): + raise NotImplementedError def calibrateFT(self, robot): """ diff --git a/python/smc/robots/robotmanager_abstract.py b/python/smc/robots/robotmanager_abstract.py index 16f8ce149161abf50c5e68dddba642bac8c54bff..28d4c8692dd1605d721b16f8c99be509d5fa71d3 100644 --- a/python/smc/robots/robotmanager_abstract.py +++ b/python/smc/robots/robotmanager_abstract.py @@ -8,7 +8,7 @@ import numpy as np from functools import partial -class RobotManagerAbstract(abc.ABC): +class AbstractRobotManager(abc.ABC): """ RobotManager: --------------- @@ -20,32 +20,52 @@ class RobotManagerAbstract(abc.ABC): - provide functions every robot has to have like getQ """ - # just pass all of the arguments here and store them as is - # so as to minimize the amount of lines. - # might be changed later if that seems more appropriate + # weird ass pythonic way to declare an abstract atribute: + # you make the property function abstract, + # and then implement that. + # the property function consists of: + # - the class attribute + # - setter of the class attribute + # - getter of the class attribute + # - deleter of the class attribute + # since we only want the class attribute which won't be changed, + # we don't explicitely declare @property.setter and the rest. + # if you don't implement the following for attributes declared this way: + # @property + # def attribute(self): + # return self.attribute + # then you will get a TypeError, and that's what we want - + # errors if you didn't implement these attributes. + + # NOTE: these arguments you need to implement yourself + @property @abc.abstractmethod + def args(self) -> argparse.Namespace: ... + + @property + @abc.abstractmethod + def model(self) -> pin.Model: ... + + @property + @abc.abstractmethod + def data(self) -> pin.Data: ... + @property + @abc.abstractmethod + def visual_model(self) -> pin.pinocchio_pywrap_default.GeometryModel: ... + @property + @abc.abstractmethod + def collision_model(self) -> pin.pinocchio_pywrap_default.GeometryModel: ... + + # NOTE: call this via super().__init__() after you've implemented the above abstract properties def __init__(self, args): - self.args: argparse.Namespace # const #################################################################### # robot-related attributes # #################################################################### self.robot_name: str = args.robot # const - self.model: pin.Model # const - self.data: pin.Data - self.visual_model: pin.pinocchio_pywrap_default.GeometryModel - self.collision_model: pin.pinocchio_pywrap_default.GeometryModel - self._dt = 1 / self.args.ctrl_freq # [s] self._t: float = 0.0 - # defined per robot - hardware limit - # TODO TODO TODO: - # these max things need to be arrays - # and if nothing is set via arguments, set it to model.velocityLimit by default!!!!!!! - # qd_cmd = np.clip( - # qd, -1 * self.model.velocityLimit, self.model.velocityLimit - # ) - # NOTE: probably should be an array, but since UR robots only accept a float, - # we go for a float + # NOTE: _MAX_ACCELERATION probably should be an array, but since UR robots only accept a float, + # we go for a float and pretend the every vector element is this 1 number # NOTE: this makes sense only for velocity-controlled robots. # when torque control robot support will be introduce, we'll do something else here self._MAX_ACCELERATION: float | np.ndarray # in joint space @@ -55,25 +75,25 @@ class RobotManagerAbstract(abc.ABC): ) self._acceleration = self.args.acceleration # _MAX_V is the robot's hardware joint velocity limit - self._MAX_V: np.ndarray - self._MAX_V = self.model.velocityLimit + self._MAX_V: np.ndarray = self.model.velocityLimit # but you usually want to run at lower speeds for safety reasons assert self.args.max_v_percentage <= 1.0 and self.args.max_v_pecentage > 0.0 # so velocity commands are internally clipped to self._max_v self._max_v = np.clip(self._MAX_V, 0.0, args.max_v_percentage * self._MAX_V) # NOTE: make sure you've read pinocchio docs and understand why # nq and nv are not necessarily the same number - self.q = np.zeros(self.model.nq) - self.v = np.zeros(self.model.nv) - self.a = np.zeros(self.model.nv) + self._q = np.zeros(self.model.nq) + self._v = np.zeros(self.model.nv) + self._a = np.zeros(self.model.nv) + # TODO: each robot should know which grippers are available + # and set this there. self.gripper = None self._step() #################################################################### # processes and utilities robotmanager owns # #################################################################### - # TODO: make a default if args.save_log: self._log_manager = LogManager(args) @@ -90,7 +110,7 @@ class RobotManagerAbstract(abc.ABC): self.visual_model, ) self.visualizer_manager = ProcessManager( - args, side_function, {"q": self.q.copy()}, 0 + args, side_function, {"q": self.q}, 0 ) # TODO: move this bs to visualizer, there are 0 reasons to keep it here if args.visualize_collision_approximation: @@ -101,7 +121,7 @@ class RobotManagerAbstract(abc.ABC): ) @abc.abstractmethod - def setInitialPose(self): + def setInitialPose(self) -> None: """ setInitialPose -------------- @@ -114,11 +134,13 @@ class RobotManagerAbstract(abc.ABC): """ pass - def getQ(self): - return self.q.copy() + @property + def q(self): + return self._q.copy() - def getV(self): - return self.v.copy() + @property + def v(self): + return self._v.copy() @abc.abstractmethod def forwardKinematics(self): @@ -130,6 +152,14 @@ class RobotManagerAbstract(abc.ABC): for a single manipulator with just the end-effector frame, this is a pointless function, but there is more to do for dual arm robots for example. + + This should be implemented by an interface. + For example, for the SingleArmInterface you would do: + + def forwardKinematics(self): + pin.forwardKinematics(self.model, self.data, self._q) + pin.updateFramePlacement(self.model, self.data, self._ee_frame_id) + self.T_w_e = self.data.oMf[self._ee_frame_id] """ pass @@ -147,25 +177,24 @@ class RobotManagerAbstract(abc.ABC): Usage: - don't call this method yourself! this is CommandLoopManager's job - use getters to get copies of the variables you want + + Look at some interfaces to see particular implementations. """ - def sendVelocityCommand(self, v): + def sendVelocityCommand(self, v) -> None: """ sendVelocityCommand - ----- - different things need to be send depending on whether you're running a simulation, - you're on a real robot, you're running some new simulator bla bla. this is handled - here because this things depend on the arguments which are manager here (hence the - class name RobotManager) + ------------------- + 1) saturate the command to comply with hardware limits or smaller limits you've set + 2) send it via the particular robot's particular communication interface """ assert type(v) == np.ndarray assert len(v) == self.model.nv v = np.clip(v, -1 * self._max_v, self._max_v) - self.actualSendCommand(v) + self.sendVelocityCommandToReal(v) @abc.abstractmethod - def actualSendCommand(self, v): - raise NotImplementedError() + def sendVelocityCommandToReal(self, v): ... # TODO: make faux gripper class to avoid this bullshit here def openGripper(self): @@ -222,7 +251,7 @@ class RobotManagerAbstract(abc.ABC): print("you didn't select viz") -class RobotManagerRealAbstract(RobotManagerAbstract, abc.ABC): +class AbstractRobotManagerReal(AbstractRobotManager, abc.ABC): """ RobotManagerRealAbstract ------------------------ @@ -276,15 +305,15 @@ class RobotManagerRealAbstract(RobotManagerAbstract, abc.ABC): """ pass - @abc.abstractmethod - def _step(self): - self._getQ() - self._getV() - # self._getWrench() - - # NOTE: when implementing an interface for torque-controlled robots, - # use computeAllTerms to make life easier - self.forwardKinematics() + # @abc.abstractmethod + # def _step(self): + # self._getQ() + # self._getV() + # # self._getWrench() + # + # # NOTE: when implementing an interface for torque-controlled robots, + # # use computeAllTerms to make life easier + # self.forwardKinematics() @abc.abstractmethod def stopRobot(self):