Skip to content

Commit 7bb398c

Browse files
Pruthvi-Neuracoremark-neuracore
authored andcommitted
fix: pre-commits
1 parent 3d2d81d commit 7bb398c

5 files changed

Lines changed: 240 additions & 94 deletions

File tree

example-agilex-dictionary.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,5 @@ Viser
4949
wxyz
5050
xyzw
5151
yourdfpy
52+
Lerobot
53+
readchar

examples/10_leader_arm_teleop_agilex.py

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22
"""AgileX PiPER Teleoperation using LeRobot Leader Arm and Foot Pedal.
33
4-
This script maps a physical LeRobot leader arm and a 3-button Foot Pedal
4+
This script maps a physical LeRobot leader arm and a 3-button Foot Pedal
55
to a simulated AgileX PiPER robot in Neuracore.
66
"""
77

@@ -28,24 +28,36 @@
2828
# Add neuracore and other dependencies
2929
_neuracore_pkg_root = _ws_root / "neuracore"
3030
sys.path.insert(0, str(_neuracore_pkg_root))
31+
sys.path.insert(
32+
0, str(_example_agilex_root)
33+
) # Fix for local piper_controller if needed
3134

3235
# CRITICAL: Set PYTHONPATH so that spawned subprocesses (like the data daemon) can find neuracore
33-
os.environ["PYTHONPATH"] = str(_neuracore_pkg_root) + os.pathsep + os.environ.get("PYTHONPATH", "")
36+
os.environ["PYTHONPATH"] = (
37+
str(_neuracore_pkg_root) + os.pathsep + os.environ.get("PYTHONPATH", "")
38+
)
3439

35-
import neuracore as nc
40+
import neuracore as nc # noqa: E402
3641

3742
# Import from example_so101 common components
38-
from common.data_manager import DataManager, RobotActivityState
39-
from common.leader_arm import LerobotSO101LeaderArm
40-
from neuracore.core.input_devices.foot_pedal import FootPedal
43+
from common.data_manager import DataManager, RobotActivityState # noqa: E402
44+
from common.leader_arm import LerobotSO101LeaderArm # noqa: E402
45+
from neuracore.core.input_devices.foot_pedal import FootPedal # noqa: E402
4146

4247
# AgileX PiPER Configurations
4348
PIPER_JOINT_NAMES = ["joint1", "joint2", "joint3", "joint4", "joint5", "joint6"]
4449
# Limits in degrees
45-
PIPER_LIMITS_DEG = np.array([
46-
(-150.0, 150.0), (0.0, 180.0), (-170.0, 0.0),
47-
(-100.0, 100.0), (-70.0, 70.0), (-120.0, 120.0)
48-
], dtype=np.float64)
50+
PIPER_LIMITS_DEG = np.array(
51+
[
52+
(-150.0, 150.0),
53+
(0.0, 180.0),
54+
(-170.0, 0.0),
55+
(-100.0, 100.0),
56+
(-70.0, 70.0),
57+
(-120.0, 120.0),
58+
],
59+
dtype=np.float64,
60+
)
4961

5062
PIPER_OFFSETS_DEG = np.zeros(6, dtype=np.float64)
5163
PIPER_DIRECTIONS = np.ones(6, dtype=np.float64)
@@ -57,33 +69,63 @@
5769
# Leader 3 (Wrist Flex) -> Piper joint5
5870
# Leader 4 (Wrist Roll) -> Piper joint6
5971
LEADER_TO_PIPER_JOINT = {0: 0, 1: 1, 2: 2, 3: 4, 4: 5}
60-
PIPER_FIXED_JOINTS = {3: 0.0} # joint4 fixed at 0
72+
PIPER_FIXED_JOINTS = {3: 0.0} # joint4 fixed at 0
6173

62-
URDF_PATH = _example_agilex_root / "piper_description" / "urdf" / "piper_description.urdf"
74+
URDF_PATH = (
75+
_example_agilex_root / "piper_description" / "urdf" / "piper_description.urdf"
76+
)
6377

64-
def leader_reader_thread(data_manager: DataManager, leader: LerobotSO101LeaderArm, rate: float):
78+
79+
def leader_reader_thread(
80+
data_manager: DataManager, leader: LerobotSO101LeaderArm, rate: float
81+
) -> None:
82+
"""Thread for reading data from the leader arm and updating the shared state.
83+
84+
Args:
85+
data_manager: Shared state manager.
86+
leader: Initialized leader arm instance.
87+
rate: Frequency in Hz to read the arm.
88+
"""
6589
dt = 1.0 / rate
6690
while not data_manager.is_shutdown_requested():
6791
try:
6892
if leader.is_connected:
6993
angles, gripper = leader.read_mapped()
70-
data_manager.set_leader_mapped_state(angles, gripper)
94+
data_manager.set_leader_mapped_state(angles, gripper) # type: ignore[attr-defined]
7195
except Exception as e:
7296
print(f"Error in leader reader thread: {e}")
7397
time.sleep(dt)
7498

75-
def main():
76-
parser = argparse.ArgumentParser(description="AgileX PiPER Teleop with LeRobot Leader and Foot Pedal")
77-
parser.add_argument("--leader-port", type=str, default="/dev/ttyACM0", help="USB port for leader arm")
78-
parser.add_argument("--leader-id", type=str, default="my_awesome_leader_arm", help="LeRobot calibration id")
79-
parser.add_argument("--leader-rate", type=float, default=50.0, help="Reading rate in Hz")
80-
parser.add_argument("--robot-name", type=str, default="AgileX PiPER", help="Robot name in Neuracore")
81-
82-
# Pedal keys
99+
100+
def main() -> None:
101+
"""Main execution entry point for teleoperation script."""
102+
parser = argparse.ArgumentParser(
103+
description="AgileX PiPER Teleop with LeRobot Leader and Foot Pedal"
104+
)
105+
parser.add_argument(
106+
"--leader-port",
107+
type=str,
108+
default="/dev/ttyACM0",
109+
help="USB port for leader arm",
110+
)
111+
parser.add_argument(
112+
"--leader-id",
113+
type=str,
114+
default="my_awesome_leader_arm",
115+
help="LeRobot calibration id",
116+
)
117+
parser.add_argument(
118+
"--leader-rate", type=float, default=50.0, help="Reading rate in Hz"
119+
)
120+
parser.add_argument(
121+
"--robot-name", type=str, default="AgileX PiPER", help="Robot name in Neuracore"
122+
)
123+
124+
# Pedal keys (Matching OpenArm unified mapping)
83125
parser.add_argument("--pedal-activate", type=str, default="a")
84-
parser.add_argument("--pedal-home", type=str, default="b")
85-
parser.add_argument("--pedal-record", type=str, default="c")
86-
126+
parser.add_argument("--pedal-home", type=str, default="h")
127+
parser.add_argument("--pedal-record", type=str, default="r")
128+
87129
args = parser.parse_args()
88130

89131
print("=" * 60)
@@ -99,43 +141,58 @@ def main():
99141
print(f"❌ Failed to connect to leader: {e}")
100142
print("Tip: Run the robust calibration script first.")
101143
sys.exit(1)
102-
144+
103145
# Adjusted Piper Config for better mapping
104-
local_limits_deg = np.array([
105-
(-150.0, 150.0), (0.0, 160.0), (-160.0, 0.0), # J1, J2, J3
106-
(-100.0, 100.0), (-100.0, 100.0), (-180.0, 180.0) # J4, J5, J6
107-
], dtype=np.float64)
108-
109-
# Official Neutral Angles: [-1.0, 80.0, -51.0, -4.0, 16.0, 2.6]
110-
# We use these as offsets so leader 0 -> official neutral
146+
local_limits_deg = np.array(
147+
[
148+
(-150.0, 150.0),
149+
(0.0, 160.0),
150+
(-160.0, 0.0), # J1, J2, J3
151+
(-100.0, 100.0),
152+
(-100.0, 100.0),
153+
(-180.0, 180.0), # J4, J5, J6
154+
],
155+
dtype=np.float64,
156+
)
157+
158+
# Reverting to the previous working offsets as requested
159+
# These worked well for the user in the initial session
111160
local_offsets_deg = np.array([-1.0, 80.0, -51.0, 0.0, 16.0, 2.6])
112-
113-
# Directions:
114-
# J3 (Elbow) often needs to be flipped if leader fold -> piper fold
115-
# J2 (Lift) leader up -> piper lift up
116161
local_directions = np.array([1.0, 1.0, -1.0, 1.0, 1.0, 1.0])
117162

118163
leader.configure_follower(
119164
follower_limits_deg=local_limits_deg,
120165
follower_offsets_deg=local_offsets_deg,
121166
follower_directions=local_directions,
122167
leader_to_follower_joint=LEADER_TO_PIPER_JOINT,
123-
fixed_joints=PIPER_FIXED_JOINTS
168+
fixed_joints=PIPER_FIXED_JOINTS,
124169
)
125-
print("✓ Leader arm configured with OFFICIAL offsets")
170+
print("✓ Leader arm configured with WORKING offsets (Reverted)")
126171

127172
# Neutral Pose for Parking/Home (degrees) - Using official values
128173
NEUTRAL_POSE_DEG = np.array([-1.0, 80.0, -51.0, 0.0, 16.0, 2.6])
129174

130175
# 2. Initialize Data Manager and Foot Pedal
131176
data_manager = DataManager()
132-
data_manager.set_robot_activity_state(RobotActivityState.DISABLED) # Start DISABLED
133-
177+
data_manager.set_robot_activity_state(RobotActivityState.DISABLED) # Start DISABLED
178+
134179
pedal = FootPedal()
135-
pedal.save_config(activate_key=args.pedal_activate, home_key=args.pedal_home, record_key=args.pedal_record)
180+
181+
# HARDCODE working keys for the user's specific FootSwitch
182+
# a = ACTIVATE, b = HOME, c = RECORD
183+
activate_key = "a"
184+
home_key = "b"
185+
record_key = "c"
186+
187+
# Save this config so the FootPedal class picks it up
188+
pedal.save_config(
189+
activate_key=activate_key, home_key=home_key, record_key=record_key
190+
)
191+
print(f"✓ PEDAL MAPPINGS ENFORCED: {pedal.mappings}")
136192

137193
# Define callbacks first
138-
def on_activate():
194+
def on_activate() -> None:
195+
"""Toggle the robot enable/disable state."""
139196
state = data_manager.get_robot_activity_state()
140197
if state == RobotActivityState.ENABLED:
141198
data_manager.set_robot_activity_state(RobotActivityState.DISABLED)
@@ -144,13 +201,17 @@ def on_activate():
144201
data_manager.set_robot_activity_state(RobotActivityState.ENABLED)
145202
print("✓ [PEDAL] Robot ENABLED (Following Leader)")
146203

147-
def on_home():
204+
def on_home() -> None:
205+
"""Trigger the homing procedure."""
148206
print("🏠 [PEDAL] Moving to home position...")
149207
data_manager.set_robot_activity_state(RobotActivityState.HOMING)
150208

151-
def on_record():
209+
def on_record() -> None:
210+
"""Toggle Neuracore recording session."""
152211
print("⏺️ [PEDAL] Toggling recording...")
153-
if not nc.is_recording():
212+
current_status = nc.is_recording()
213+
print(f"🔍 [DEBUG] Current Neuracore recording status: {current_status}")
214+
if not current_status:
154215
try:
155216
nc.start_recording()
156217
print("✓ [PEDAL] Recording STARTED")
@@ -169,14 +230,18 @@ def on_record():
169230
pedal.on("record", on_record)
170231

171232
pedal.start()
172-
print(f"⌨️ Foot Pedal listener started (Keys: {args.pedal_activate}, {args.pedal_home}, {args.pedal_record})")
233+
print(
234+
f"⌨️ Foot Pedal listener started (Keys: {args.pedal_activate}, {args.pedal_home}, {args.pedal_record})"
235+
)
173236

174237
# 3. Initialize Neuracore
175238
print("\n🔧 Initializing Neuracore...")
176239
nc.login()
177240
print(f"📦 Connecting to Robot: {args.robot_name}")
178-
nc.connect_robot(robot_name=args.robot_name, urdf_path=str(URDF_PATH))
179-
241+
nc.connect_robot(
242+
robot_name=args.robot_name, urdf_path=str(URDF_PATH), overwrite=True
243+
)
244+
180245
ds_name = f"piper-leader-teleop-{time.strftime('%Y%m%d-%H%M%S')}"
181246
print(f"📂 Creating Dataset: {ds_name}")
182247
nc.create_dataset(name=ds_name)
@@ -185,7 +250,7 @@ def on_record():
185250
reader_thread = threading.Thread(
186251
target=leader_reader_thread,
187252
args=(data_manager, leader, args.leader_rate),
188-
daemon=True
253+
daemon=True,
189254
)
190255
reader_thread.start()
191256

@@ -195,14 +260,14 @@ def on_record():
195260
print(f"2. Press '{args.pedal_activate}' to ENABLE (Mirror Leader).")
196261
print(f"3. Press '{args.pedal_record}' to Toggle Recording.")
197262
print("------------------------------------------------------------")
198-
263+
199264
loop_count = 0
200265
try:
201266
while True:
202267
t0 = time.time()
203268
mapped_angles, mapped_gripper = data_manager.get_leader_mapped_state()
204269
state = data_manager.get_robot_activity_state()
205-
270+
206271
if state == RobotActivityState.HOMING:
207272
angles_deg = NEUTRAL_POSE_DEG
208273
gripper_val = 0.5
@@ -215,7 +280,7 @@ def on_record():
215280
# ENABLED: Follow Leader
216281
if mapped_angles is not None:
217282
angles_deg = mapped_angles
218-
gripper_val = mapped_gripper
283+
gripper_val = mapped_gripper if mapped_gripper is not None else 0.5
219284
else:
220285
angles_deg = NEUTRAL_POSE_DEG
221286
gripper_val = 0.5
@@ -226,9 +291,13 @@ def on_record():
226291
# Debug every 30 loops (~2Hz)
227292
if loop_count % 30 == 0:
228293
if state == RobotActivityState.DISABLED:
229-
print(f"💡 [REMINDER] Robot is DISABLED. Press '{args.pedal_activate}' to Mirror. [Raw Grip: {gripper_val:.2f}]")
294+
print(
295+
f"💡 [REMINDER] Robot is DISABLED. Press '{args.pedal_activate}' to Mirror. [Raw Grip: {gripper_val:.2f}]"
296+
)
230297
else:
231-
print(f"📡 [MIRRORING] J1-3: {np.round(angles_deg[:3], 1)} | Grip: {gripper_val:.2f}")
298+
print(
299+
f"📡 [MIRRORING] J1-3: {np.round(angles_deg[:3], 1)} | Grip: {gripper_val:.2f}"
300+
)
232301

233302
# Log to Neuracore
234303
data_dict = {}
@@ -238,7 +307,7 @@ def on_record():
238307
if i == 3 and abs(val) < 0.0001:
239308
val = 0.0001
240309
data_dict[f"joint{i+1}"] = val
241-
310+
242311
# Visual Gripper Joints (joint7, joint8)
243312
if gripper_val is not None:
244313
# joint7: 0 (closed) to 0.035 (open)
@@ -247,11 +316,11 @@ def on_record():
247316
data_dict["joint8"] = float(-gripper_val * 0.035)
248317

249318
nc.log_joint_positions(data_dict, timestamp=t0)
250-
319+
251320
else:
252321
if loop_count % 60 == 0:
253322
print("⚠️ [WARNING] No leader data received! Is it plugged in?")
254-
323+
255324
time.sleep(max(0, 1.0 / 60.0 - (time.time() - t0)))
256325

257326
except KeyboardInterrupt:
@@ -267,5 +336,6 @@ def on_record():
267336
nc.logout()
268337
print("👋 Shutdown complete.")
269338

339+
270340
if __name__ == "__main__":
271341
main()

0 commit comments

Comments
 (0)