Skip to content

Commit 4175d76

Browse files
miikeeclaude
andcommitted
Add has_rail parameter to PF400 and PF3400 backends
- Add has_rail: bool = False parameter to __init__ for both backends - Override move_j() to conditionally include/exclude rail joint in moveJ command - Override set_joint_angles() to conditionally include/exclude rail in locAngles command - Fix convert_to_joint_space() to properly map all 6 wherej output values The moveJ command format differs based on rail configuration: - WITHOUT rail: moveJ {profile} {base} {shoulder} {elbow} {wrist} {gripper} - WITH rail: moveJ {profile} {rail} {base} {shoulder} {elbow} {wrist} {gripper} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c57a324 commit 4175d76

File tree

2 files changed

+133
-17
lines changed

2 files changed

+133
-17
lines changed

pylabrobot/arms/precise_flex/pf_3400.py

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@
77
class PreciseFlex3400Backend(PreciseFlexBackend):
88
"""Backend for the PreciseFlex 3400 robotic arm."""
99

10-
def __init__(self, host: str, port: int = 10100, timeout=20) -> None:
10+
def __init__(self, host: str, port: int = 10100, timeout=20, has_rail: bool = False) -> None:
1111
super().__init__(host=host, port=port, timeout=timeout)
12+
self._has_rail = has_rail
1213

1314
def convert_to_joint_space(self, position: List[float]) -> PreciseFlexJointCoords:
14-
"""Convert a tuple of joint angles to a PreciseFlexJointCoords object."""
15+
"""Convert a list of joint angles to a PreciseFlexJointCoords object.
16+
17+
Args:
18+
position: List of 6 joint angles in order: [rail, base, shoulder, elbow, wrist, gripper]
19+
This matches the output format of the wherej command.
20+
21+
Returns:
22+
PreciseFlexJointCoords with all joint values mapped correctly.
23+
"""
1524
if len(position) != 6:
16-
raise ValueError("Position must be a tuple of 6 joint angles.")
25+
raise ValueError("Position must be a list of 6 joint angles.")
1726
return PreciseFlexJointCoords(
18-
rail=0,
19-
base=position[0],
20-
shoulder=position[1],
21-
elbow=position[2],
22-
wrist=position[3],
23-
gripper=position[4],
27+
rail=position[0],
28+
base=position[1],
29+
shoulder=position[2],
30+
elbow=position[3],
31+
wrist=position[4],
32+
gripper=position[5],
2433
)
2534

2635
# def convert_to_joints_array(self, position: PreciseFlexJointCoords) -> List[float]:
@@ -33,3 +42,52 @@ def convert_to_joint_space(self, position: List[float]) -> PreciseFlexJointCoord
3342
# position.wrist,
3443
# position.gripper,
3544
# ] # PF400 has 5 joints, last is fixed
45+
46+
async def move_j(self, profile_index: int, joint_coords: PreciseFlexJointCoords) -> None:
47+
"""Move the robot using joint coordinates, handling rail configuration."""
48+
if self._has_rail:
49+
angles_str = (
50+
f"{joint_coords.rail} "
51+
f"{joint_coords.base} "
52+
f"{joint_coords.shoulder} "
53+
f"{joint_coords.elbow} "
54+
f"{joint_coords.wrist} "
55+
f"{joint_coords.gripper}"
56+
)
57+
else:
58+
# Exclude rail for robots without rail
59+
angles_str = (
60+
f"{joint_coords.base} "
61+
f"{joint_coords.shoulder} "
62+
f"{joint_coords.elbow} "
63+
f"{joint_coords.wrist} "
64+
f"{joint_coords.gripper}"
65+
)
66+
await self.send_command(f"moveJ {profile_index} {angles_str}")
67+
68+
async def set_joint_angles(
69+
self,
70+
location_index: int,
71+
joint_position: PreciseFlexJointCoords,
72+
) -> None:
73+
"""Set joint angles for stored location, handling rail configuration."""
74+
if self._has_rail:
75+
await self.send_command(
76+
f"locAngles {location_index} "
77+
f"{joint_position.rail} "
78+
f"{joint_position.base} "
79+
f"{joint_position.shoulder} "
80+
f"{joint_position.elbow} "
81+
f"{joint_position.wrist} "
82+
f"{joint_position.gripper}"
83+
)
84+
else:
85+
# Exclude rail for robots without rail
86+
await self.send_command(
87+
f"locAngles {location_index} "
88+
f"{joint_position.base} "
89+
f"{joint_position.shoulder} "
90+
f"{joint_position.elbow} "
91+
f"{joint_position.wrist} "
92+
f"{joint_position.gripper}"
93+
)

pylabrobot/arms/precise_flex/pf_400.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@
77
class PreciseFlex400Backend(PreciseFlexBackend):
88
"""Backend for the PreciseFlex 400 robotic arm."""
99

10-
def __init__(self, host: str, port: int = 10100, timeout=20) -> None:
10+
def __init__(self, host: str, port: int = 10100, timeout=20, has_rail: bool = False) -> None:
1111
super().__init__(host=host, port=port, timeout=timeout)
12+
self._has_rail = has_rail
1213

1314
def convert_to_joint_space(self, position: List[float]) -> PreciseFlexJointCoords:
14-
"""Convert a list of joint angles to a PreciseFlexJointCoords object."""
15+
"""Convert a list of joint angles to a PreciseFlexJointCoords object.
16+
17+
Args:
18+
position: List of 6 joint angles in order: [rail, base, shoulder, elbow, wrist, gripper]
19+
This matches the output format of the wherej command.
20+
21+
Returns:
22+
PreciseFlexJointCoords with all joint values mapped correctly.
23+
"""
1524
if len(position) != 6:
1625
raise ValueError("Position must be a list of 6 joint angles.")
1726
return PreciseFlexJointCoords(
18-
rail=0,
19-
base=position[0],
20-
shoulder=position[1],
21-
elbow=position[2],
22-
wrist=position[3],
23-
gripper=0,
27+
rail=position[0],
28+
base=position[1],
29+
shoulder=position[2],
30+
elbow=position[3],
31+
wrist=position[4],
32+
gripper=position[5],
2433
)
2534

2635
# def convert_to_joints_array(self, position: PreciseFlexJointCoords) -> List[float]:
@@ -33,3 +42,52 @@ def convert_to_joint_space(self, position: List[float]) -> PreciseFlexJointCoord
3342
# position.wrist,
3443
# 0,
3544
# ] # PF400 has 4 joints, last two are fixed
45+
46+
async def move_j(self, profile_index: int, joint_coords: PreciseFlexJointCoords) -> None:
47+
"""Move the robot using joint coordinates, handling rail configuration."""
48+
if self._has_rail:
49+
angles_str = (
50+
f"{joint_coords.rail} "
51+
f"{joint_coords.base} "
52+
f"{joint_coords.shoulder} "
53+
f"{joint_coords.elbow} "
54+
f"{joint_coords.wrist} "
55+
f"{joint_coords.gripper}"
56+
)
57+
else:
58+
# Exclude rail for robots without rail
59+
angles_str = (
60+
f"{joint_coords.base} "
61+
f"{joint_coords.shoulder} "
62+
f"{joint_coords.elbow} "
63+
f"{joint_coords.wrist} "
64+
f"{joint_coords.gripper}"
65+
)
66+
await self.send_command(f"moveJ {profile_index} {angles_str}")
67+
68+
async def set_joint_angles(
69+
self,
70+
location_index: int,
71+
joint_position: PreciseFlexJointCoords,
72+
) -> None:
73+
"""Set joint angles for stored location, handling rail configuration."""
74+
if self._has_rail:
75+
await self.send_command(
76+
f"locAngles {location_index} "
77+
f"{joint_position.rail} "
78+
f"{joint_position.base} "
79+
f"{joint_position.shoulder} "
80+
f"{joint_position.elbow} "
81+
f"{joint_position.wrist} "
82+
f"{joint_position.gripper}"
83+
)
84+
else:
85+
# Exclude rail for robots without rail
86+
await self.send_command(
87+
f"locAngles {location_index} "
88+
f"{joint_position.base} "
89+
f"{joint_position.shoulder} "
90+
f"{joint_position.elbow} "
91+
f"{joint_position.wrist} "
92+
f"{joint_position.gripper}"
93+
)

0 commit comments

Comments
 (0)