Skip to content

Commit c58a11f

Browse files
committed
added control script for full head and dual arm setup; bug fix in ur placo end effector frame visualization
1 parent 28bf7ae commit c58a11f

File tree

3 files changed

+122
-61
lines changed

3 files changed

+122
-61
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import argparse
2+
from teleop_demo_python.hardware.dynamixel import DynamixelHeadController
3+
from teleop_demo_python.utils.pico_client import PicoClient
4+
from teleop_demo_python.hardware.ur import DualArmURController
5+
6+
import threading
7+
8+
9+
def main():
10+
parser = argparse.ArgumentParser(
11+
description="Run head and dual arm teleoperation control."
12+
)
13+
parser.add_argument(
14+
"--reset",
15+
action="store_true",
16+
help="Run the reset procedure for the dual arm controller.",
17+
)
18+
args = parser.parse_args()
19+
20+
pico_client = PicoClient()
21+
22+
# Initialize controllers after parsing args, in case reset needs them
23+
# Or, if reset can be done without full init, adjust accordingly
24+
head_controller = DynamixelHeadController(pico_client)
25+
arm_controller = DualArmURController(pico_client)
26+
27+
if args.reset:
28+
print("Reset flag detected. Running arm controller reset procedure...")
29+
try:
30+
arm_controller.reset()
31+
print("Arm controller reset procedure completed.")
32+
except Exception as e:
33+
print(f"Error during arm_controller.reset(): {e}")
34+
35+
else:
36+
print("No reset flag detected. Proceeding with normal operation.")
37+
arm_controller.calc_target_joint_position()
38+
39+
stop_signal = threading.Event()
40+
head_thread = threading.Thread(
41+
target=head_controller.run_thread,
42+
args=(stop_signal,),
43+
)
44+
left_arm_thread = threading.Thread(
45+
target=arm_controller.run_left_controller_thread,
46+
args=(stop_signal,),
47+
)
48+
right_arm_thread = threading.Thread(
49+
target=arm_controller.run_right_controller_thread,
50+
args=(stop_signal,),
51+
)
52+
53+
# Start the threads
54+
head_thread.start()
55+
left_arm_thread.start()
56+
right_arm_thread.start()
57+
58+
while not stop_signal.is_set():
59+
try:
60+
arm_controller.calc_target_joint_position()
61+
except KeyboardInterrupt:
62+
print("KeyboardInterrupt detected. Exiting...")
63+
stop_signal.set() # Trigger the stop signal for all threads
64+
65+
head_thread.join()
66+
left_arm_thread.join()
67+
right_arm_thread.join()
68+
69+
head_controller.close()
70+
arm_controller.close()
71+
print("All controllers have been stopped and disconnected.")
72+
73+
74+
if __name__ == "__main__":
75+
main()

teleop_demo_python/hardware/dynamixel.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,6 @@ def close(self):
155155
finally:
156156
self.is_closed = True
157157

158-
def __del__(self):
159-
"""Ensures resources are released when the object is deleted."""
160-
self.close()
161-
162158

163159
class DynamixelHeadController:
164160
"""Specific controller for yaw-pitch head control using two Dynamixel motors."""
@@ -254,22 +250,21 @@ def get_target_orientation(self) -> tuple:
254250
print(f"Error fetching head orientation: {e}")
255251
return 0.0, 0.0 # Default values in case of error
256252

257-
def run(self):
258-
stop_event = threading.Event()
253+
def run(self, stop_event: threading.Event = threading.Event()):
259254
control_thread = threading.Thread(
260255
target=self.run_thread,
261256
args=(stop_event,),
262257
)
263258
control_thread.start()
264259

265-
while True:
260+
while not stop_event.is_set():
266261
try:
267-
time.sleep(0.1)
262+
time.sleep(0.01)
268263
except KeyboardInterrupt:
269264
print("Stopping head control...")
270265
stop_event.set()
271-
control_thread.join()
272-
break
266+
267+
control_thread.join()
273268

274269
def run_thread(self, stop_event: threading.Event):
275270
"""

teleop_demo_python/hardware/ur.py

Lines changed: 42 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import time
3+
import webbrowser
24
import rtde_control
35
import rtde_receive
46
import numpy as np
@@ -27,15 +29,15 @@
2729
LEFT_ROBOT_IP = "192.168.50.55"
2830
RIGHT_ROBOT_IP = "192.168.50.195"
2931

30-
SERVO_TIME = 0.017 # 17ms (60Hz)
31-
LOOKAHEAD_TIME = 0.1 # 100ms look ahead
32-
SERVO_GAIN = 300.0 # Servo gain
33-
MAX_VELOCITY = 0.5 # 0.5 m/s
34-
MAX_ACCELERATION = 1.0 # 1.0 m/s^2
32+
SERVO_TIME = 0.017
33+
LOOKAHEAD_TIME = 0.1
34+
SERVO_GAIN = 300.0
35+
MAX_VELOCITY = 0.5
36+
MAX_ACCELERATION = 1.0
3537

3638
GRIPPER_FORCE = 128
3739
GRIPPER_SPEED = 255
38-
CONTROLLER_DEADZONE = 0.1 # Deadzone for controller input
40+
CONTROLLER_DEADZONE = 0.1
3941

4042
LEFT_INITIAL_JOINT_DEG = np.array(
4143
[165.26, -47.50, 118.93, -38.96, 87.51, 149.56]
@@ -83,15 +85,16 @@ def __init__(
8385

8486
self.gripper = RobotiqGripper()
8587
self.gripper.connect(robot_ip, 63352)
86-
self.gripper.activate()
8788
print("Gripper connected.")
8889

89-
def move_to_initial_position(self):
90+
def reset(self):
9091
print(
9192
f"Moving to initial joint positions: {self.initial_joint_positions}"
9293
)
9394
self.rtde_c.moveJ(self.initial_joint_positions)
9495
print("Reached initial position.")
96+
self.gripper.activate()
97+
print("Gripper activated.")
9598

9699
def servo_joints(self, joint_positions: np.ndarray):
97100
t_start = self.rtde_c.initPeriod()
@@ -125,11 +128,11 @@ def get_current_joint_positions(self) -> np.ndarray:
125128
def get_current_tcp_pose(self) -> np.ndarray:
126129
return np.array(self.rtde_r.getActualTCPPose())
127130

128-
def disconnect(self):
131+
def close(self):
129132
self.rtde_c.servoStop()
130133
self.rtde_c.stopScript()
131134
self.gripper.disconnect()
132-
print("UR controller stopped and gripper disconnected.")
135+
print("UR controller closed and gripper disconnected.")
133136

134137

135138
class DualArmURController:
@@ -184,16 +187,16 @@ def __init__(
184187
# Placo Setup
185188
self.placo_robot = placo.RobotWrapper(self.robot_urdf_path)
186189
self.solver = placo.KinematicsSolver(self.placo_robot)
187-
self.solver.dt = servo_time # Or use servo_time
188-
self.solver.mask_fbase(True) # UR arms are fixed base
190+
self.solver.dt = servo_time
191+
self.solver.mask_fbase(True)
189192
self.solver.add_kinetic_energy_regularization_task(1e-6)
190193

191194
# Define end-effector configuration (adjust link names and pico sources as needed)
192195
self.end_effector_config = {
193196
"left_arm": {
194-
"link_name": "left_tool0", # Link name in your URDF for left EE
195-
"pose_source": "left_controller", # Key in pico_client for left controller pose
196-
"control_trigger": "left_grip", # Key for left grip
197+
"link_name": "left_tool0",
198+
"pose_source": "left_controller",
199+
"control_trigger": "left_grip",
197200
"gripper_trigger": "left_trigger",
198201
"vis_target": "left_target",
199202
},
@@ -212,8 +215,6 @@ def __init__(
212215
self.init_controller_xyz = {}
213216
self.init_controller_quat = {}
214217
for name, config in self.end_effector_config.items():
215-
# Initialize tasks with current robot pose (or a default if robot not moved yet)
216-
# For simplicity, initializing with identity, will be updated on first activation
217218
initial_pose = np.eye(4)
218219
self.effector_task[name] = self.solver.add_frame_task(
219220
config["link_name"], initial_pose
@@ -246,6 +247,13 @@ def __init__(
246247
if self.visualize_placo:
247248
self.placo_robot.update_kinematics()
248249
self.placo_vis = robot_viz(self.placo_robot)
250+
251+
# Automatically open browser window
252+
time.sleep(0.5) # Small delay to ensure server is ready
253+
meshcat_url = self.placo_vis.viewer.url()
254+
print(f"Automatically opening meshcat at: {meshcat_url}")
255+
webbrowser.open(meshcat_url)
256+
249257
self.placo_vis.display(self.placo_robot.state.q)
250258
for name, config in self.end_effector_config.items():
251259
robot_frame_viz(self.placo_robot, config["link_name"])
@@ -387,6 +395,7 @@ def calc_target_joint_position(self):
387395
if self.visualize_placo and hasattr(self, "placo_vis"):
388396
self.placo_vis.display(self.placo_robot.state.q)
389397
for name, config in self.end_effector_config.items():
398+
robot_frame_viz(self.placo_robot, config["link_name"])
390399
frame_viz(
391400
f"target_{name}",
392401
self.effector_task[name].T_world_frame,
@@ -401,13 +410,13 @@ def calc_target_joint_position(self):
401410
f"An unexpected error occurred in IK: {e}. Returning last known good joint positions."
402411
)
403412

404-
def move_to_initial_positions(self):
405-
self.left_controller.move_to_initial_position()
406-
self.right_controller.move_to_initial_position()
413+
def reset(self):
414+
self.left_controller.reset()
415+
self.right_controller.reset()
407416

408-
def disconnect(self):
409-
self.left_controller.disconnect()
410-
self.right_controller.disconnect()
417+
def close(self):
418+
self.left_controller.close()
419+
self.right_controller.close()
411420

412421
def run_left_controller_thread(self, stop_event):
413422
print("Starting left arm control thread...")
@@ -418,7 +427,7 @@ def run_left_controller_thread(self, stop_event):
418427
self.left_controller.gripper_speed,
419428
self.left_controller.gripper_force,
420429
)
421-
self.left_controller.disconnect()
430+
self.left_controller.close()
422431

423432
def run_right_controller_thread(self, stop_event):
424433
print("Starting right arm control thread...")
@@ -429,40 +438,23 @@ def run_right_controller_thread(self, stop_event):
429438
self.right_controller.gripper_speed,
430439
self.right_controller.gripper_force,
431440
)
432-
self.right_controller.disconnect()
441+
self.right_controller.close()
433442

434-
def run(self): # Modified to remove get_joint_positions_func
443+
def run(self, stop_event=threading.Event()):
435444
try:
436-
self.move_to_initial_positions()
437-
# Ensure Placo's initial q state matches the robot after moving to initial
438-
left_q_init_actual = (
439-
self.left_controller.get_current_joint_positions()
440-
)
441-
print(f"Left initial joint positions: {left_q_init_actual}")
442-
right_q_init_actual = (
443-
self.right_controller.get_current_joint_positions()
444-
)
445-
print(f"Right initial joint positions: {right_q_init_actual}")
446-
447-
self.placo_robot.state.q[7:13] = left_q_init_actual
448-
self.placo_robot.state.q[13:19] = right_q_init_actual
449-
450-
self.placo_robot.update_kinematics() # Update placo model with these initial positions
451-
452-
self.target_left_q = left_q_init_actual.copy()
453-
self.target_right_q = right_q_init_actual.copy()
445+
self.reset()
446+
self.calc_target_joint_position()
454447

455448
except Exception as e:
456449
print(
457450
f"Error moving to initial positions or setting up Placo initial state: {e}"
458451
)
459-
self.disconnect()
452+
self.close()
460453
return
461454

462455
print("Starting dual-arm control loop...")
463456

464457
try:
465-
stop_event = threading.Event()
466458
left_thread = threading.Thread(
467459
target=self.run_left_controller_thread,
468460
args=(stop_event,),
@@ -480,11 +472,10 @@ def run(self): # Modified to remove get_joint_positions_func
480472
except KeyboardInterrupt:
481473
print("Keyboard interrupt received. Stopping control loop.")
482474
stop_event.set()
483-
left_thread.join()
484-
right_thread.join()
485-
except KeyboardInterrupt:
486-
print("Keyboard interrupt received. Stopping control loop.")
475+
476+
left_thread.join()
477+
right_thread.join()
487478
except Exception as e:
488479
print(f"Exception during control loop: {e}")
489480

490-
self.disconnect()
481+
self.close()

0 commit comments

Comments
 (0)