From 9d84e7dbd4f58d249e40b82b75989b29d123c7c2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 22:23:22 +0000
Subject: [PATCH 1/4] Initial plan
From 39d0956bbc7fa30676e678444893b8092cfb4ade Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 22:34:03 +0000
Subject: [PATCH 2/4] Add XArm5 module with comprehensive documentation and
examples
Co-authored-by: sgbaird <45469701+sgbaird@users.noreply.github.com>
---
CHANGELOG.md | 7 +
docs/_static/images/README_xarm5.txt | 12 +
docs/index.md | 1 +
setup.cfg | 3 +
src/ac_training_lab/xarm5/README.md | 290 ++++++++++++++++++
src/ac_training_lab/xarm5/__init__.py | 7 +
.../xarm5/_scripts/basic_movement.py | 118 +++++++
.../xarm5/_scripts/gripper_demo.py | 207 +++++++++++++
.../xarm5/_scripts/safety_demo.py | 240 +++++++++++++++
.../xarm5/_scripts/track_integration.py | 197 ++++++++++++
src/ac_training_lab/xarm5/requirements.txt | 7 +
11 files changed, 1089 insertions(+)
create mode 100644 docs/_static/images/README_xarm5.txt
create mode 100644 src/ac_training_lab/xarm5/README.md
create mode 100644 src/ac_training_lab/xarm5/__init__.py
create mode 100644 src/ac_training_lab/xarm5/_scripts/basic_movement.py
create mode 100644 src/ac_training_lab/xarm5/_scripts/gripper_demo.py
create mode 100644 src/ac_training_lab/xarm5/_scripts/safety_demo.py
create mode 100644 src/ac_training_lab/xarm5/_scripts/track_integration.py
create mode 100644 src/ac_training_lab/xarm5/requirements.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db43d22d..241ae4c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
## [Unreleased]
### Added
+- **XArm5 Integration**: Complete module for controlling UFACTORY xArm5 robotic arm from Raspberry Pi 5 (`src/ac_training_lab/xarm5/`)
+ - Comprehensive README with setup instructions for Raspberry Pi 5
+ - Requirements file with xarm-python-sdk dependency
+ - Example scripts for basic movement, gripper control, linear track integration, and safety features
+ - Support for rail/lift/arm combo integration with linear track control
+ - Network configuration guide for xArm5 control box
+ - Troubleshooting and safety documentation
- Support for both `rpicam-vid` (Raspberry Pi OS Trixie) and `libcamera-vid` (Raspberry Pi OS Bookworm) camera commands in `src/ac_training_lab/picam/device.py` to ensure compatibility across different OS versions.
- Comprehensive Unit Operations section in `docs/index.md` documenting all available capabilities including dispensing, synthesis, characterization, and robotics operations.
- Expanded Training Workflows section in `docs/index.md` with 10 educational workflows including RGB/RYB color matching, titration, yeast growth optimization, vision-enabled 3D printing optimization, microscopy image stitching, and AprilTag robot path planning.
diff --git a/docs/_static/images/README_xarm5.txt b/docs/_static/images/README_xarm5.txt
new file mode 100644
index 00000000..8ce43acb
--- /dev/null
+++ b/docs/_static/images/README_xarm5.txt
@@ -0,0 +1,12 @@
+Note: An image file "ufactory-xarm5.png" needs to be added to this directory.
+
+The image should be:
+- A photo or render of the UFACTORY xArm5 robotic arm
+- Approximately 50px height (width auto-scaled)
+- PNG or WebP format
+- Can be sourced from:
+ - UFACTORY official website: https://www.ufactory.cc/product-page/ufactory-xarm-5
+ - Product documentation
+ - Community resources
+
+Until the image is added, the docs/index.md table entry references this image file.
diff --git a/docs/index.md b/docs/index.md
index f28aaa3b..723a95ee 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -50,6 +50,7 @@ Here are some of the modules we have procured and are in the process of setting
| [Hiwonder ArmPi FPV](https://www.hiwonder.com/products/armpi?_pos=4&_sid=a9741a308&_ss=r) |
| 1 | An educational six-axis robotic arm | |
| [Hiwonder JetAuto Pro](https://www.hiwonder.com/products/jetauto-pro?variant=40040875229271) |
| 1 | An educational six-axis mobile cobot with a 3D depth camera and lidar | |
| [MyCobot 280](https://shop.elephantrobotics.com/en-ca/collections/mycobot/products/mycobot-pi-worlds-smallest-and-lightest-six-axis-collaborative-robot) and [MyAGV](https://shop.elephantrobotics.com/en-ca/collections/myagv/products/myagv-2023-pi?variant=47262714069304) |
| 1 | An educational six-axis mobile cobot | |
+| [UFACTORY xArm5](https://www.ufactory.cc/product-page/ufactory-xarm-5) ([docs](../src/ac_training_lab/xarm5/)) | 𦾠| 1 | A 5-DOF collaborative robotic arm controlled from Raspberry Pi 5. Supports gripper, linear track integration, and network-based control for rail/lift/arm combo systems. | |
| [AutoTrickler v4](https://autotrickler.com/pages/autotrickler-v4) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
| [ChargeMaster Supreme](https://www.rcbs.com/priming-and-powder-charging/powder-dispensers-and-scales/chargemaster-supreme-electronic-powder-dispenser/16-98943.html) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
| [Ingenuity Powder System](https://ingenuityprecision.com/product/ingenuity-powder-system/) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
diff --git a/setup.cfg b/setup.cfg
index 1e2902a2..46e23589 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -74,6 +74,9 @@ a1-cam =
boto3
wget
+xarm5 =
+ xarm-python-sdk>=1.17.0
+
[options.entry_points]
# Add here console scripts like:
# console_scripts =
diff --git a/src/ac_training_lab/xarm5/README.md b/src/ac_training_lab/xarm5/README.md
new file mode 100644
index 00000000..bfd26bfa
--- /dev/null
+++ b/src/ac_training_lab/xarm5/README.md
@@ -0,0 +1,290 @@
+# XArm5 Control for Raspberry Pi 5
+
+This module provides integration for controlling the UFACTORY xArm5 robotic arm from a Raspberry Pi 5 as part of the AC Training Lab's rail/lift/arm combo system.
+
+## Hardware Overview
+
+The **xArm5** is a 5-degree-of-freedom collaborative robotic arm manufactured by UFACTORY. It features:
+- 5 servo joints with precise control
+- Network-based control via TCP/IP
+- Support for various end effectors (grippers, sensors)
+- Compatible with linear tracks and additional accessories
+- Payload capacity suitable for laboratory automation tasks
+
+## Prerequisites
+
+### Hardware Requirements
+- Raspberry Pi 5 (4GB or 8GB RAM recommended)
+- UFACTORY xArm5 robotic arm with control box
+- Ethernet connection (recommended) or WiFi
+- Power supply for both Raspberry Pi and xArm5
+- Optional: Linear track for extended workspace
+- Optional: Gripper or end effector
+
+### Software Requirements
+- Raspberry Pi OS (Bookworm or later)
+- Python 3.8 or higher
+- Network connectivity between Raspberry Pi and xArm5 control box
+
+## Installation on Raspberry Pi 5
+
+### 1. Set Up Raspberry Pi OS
+
+If starting fresh, use the [Raspberry Pi Imager](https://www.raspberrypi.org/software/) to install Raspberry Pi OS on a microSD card.
+
+```bash
+# Update system packages
+sudo apt update
+sudo apt upgrade -y
+```
+
+### 2. Install Python Dependencies
+
+```bash
+# Install Python virtual environment tools
+sudo apt install python3-pip python3-venv -y
+
+# Create a virtual environment (recommended for Bookworm OS)
+python3 -m venv ~/xarm-env
+source ~/xarm-env/bin/activate
+```
+
+### 3. Install xArm Python SDK
+
+**Option A: Install from PyPI (Recommended)**
+```bash
+pip install xarm-python-sdk
+```
+
+**Option B: Install from source**
+```bash
+git clone https://github.com/xArm-Developer/xArm-Python-SDK.git
+cd xArm-Python-SDK
+pip install .
+```
+
+### 4. Install AC Training Lab Package (Optional)
+
+To use this module as part of the ac-training-lab package:
+```bash
+# Clone the ac-training-lab repository
+git clone https://github.com/AccelerationConsortium/ac-dev-lab.git
+cd ac-dev-lab
+
+# Install with xarm5 dependencies
+pip install -e .
+```
+
+## Network Configuration
+
+### Finding Your xArm5 IP Address
+
+1. Check the xArm5 control box display panel
+2. Or check your router's DHCP client list
+3. Or use network scanning tools:
+ ```bash
+ sudo apt install nmap
+ nmap -sn 192.168.1.0/24 # Adjust subnet to match your network
+ ```
+
+### Static IP Configuration (Recommended)
+
+For reliable automation, configure a static IP for the xArm5 control box:
+1. Access the control box web interface
+2. Navigate to Network Settings
+3. Set a static IP address (e.g., 192.168.1.100)
+4. Configure gateway and DNS if needed
+
+## Quick Start Examples
+
+### Basic Connection Test
+
+```python
+from xarm.wrapper import XArmAPI
+
+# Replace with your xArm5 control box IP
+arm = XArmAPI('192.168.1.100')
+
+# Check connection and get robot info
+print(f"Connected: {arm.connected}")
+print(f"Version: {arm.version}")
+print(f"State: {arm.state}")
+
+# Disconnect
+arm.disconnect()
+```
+
+### Simple Movement Example
+
+```python
+from xarm.wrapper import XArmAPI
+
+# Connect to xArm5
+arm = XArmAPI('192.168.1.100')
+
+# Enable motion
+arm.motion_enable(enable=True)
+arm.set_mode(0) # Position control mode
+arm.set_state(0) # Ready state
+
+# Move to home position
+arm.move_gohome(wait=True)
+
+# Move to a specific position (x, y, z in mm, angles in degrees)
+arm.set_position(x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True)
+
+# Get current position
+position = arm.get_position()
+print(f"Current position: {position}")
+
+# Disconnect
+arm.disconnect()
+```
+
+### Using with Gripper
+
+```python
+from xarm.wrapper import XArmAPI
+
+# Connect
+arm = XArmAPI('192.168.1.100')
+
+# Enable gripper
+arm.set_gripper_enable(True)
+arm.set_gripper_mode(0)
+
+# Open gripper
+arm.set_gripper_position(800, wait=True) # 800 = fully open
+
+# Close gripper
+arm.set_gripper_position(0, wait=True) # 0 = fully closed
+
+# Disconnect
+arm.disconnect()
+```
+
+## Rail/Lift/Arm Integration
+
+For integration with linear tracks and vertical lifts:
+
+### Linear Track Control
+
+```python
+from xarm.wrapper import XArmAPI
+
+arm = XArmAPI('192.168.1.100', enable_track=True)
+
+# Enable linear motor (track)
+arm.set_linear_motor_enable(True)
+
+# Move track to specific position (in mm)
+arm.set_linear_motor_pos(500, wait=True)
+
+# Get track position
+track_pos = arm.get_linear_motor_pos()
+print(f"Track position: {track_pos} mm")
+```
+
+## Example Scripts
+
+See the `_scripts/` directory for more examples:
+- `basic_movement.py` - Basic arm movements
+- `gripper_demo.py` - Gripper control demonstrations
+- `track_integration.py` - Linear track coordination
+- `safety_demo.py` - Emergency stop and safety features
+
+## Safety Considerations
+
+ā ļø **IMPORTANT SAFETY WARNINGS:**
+
+1. **Keep clear of the robot workspace** during operation
+2. **Test in simulation first** before running on real hardware
+3. **Use emergency stop** features in your code
+4. **Start with slow speeds** when testing new movements
+5. **Monitor the robot** during initial testing
+6. **Set appropriate workspace limits** to prevent collisions
+7. **Enable collision detection** when available
+
+### Emergency Stop
+
+```python
+# In case of emergency
+arm.emergency_stop()
+
+# Reset after emergency stop
+arm.clean_error()
+arm.set_state(0)
+```
+
+## Troubleshooting
+
+### Connection Issues
+
+**Problem:** Cannot connect to xArm5
+- Verify both devices are on the same network
+- Check IP address is correct
+- Ping the control box: `ping 192.168.1.100`
+- Check firewall settings on Raspberry Pi
+- Ensure control box is powered on and initialized
+
+**Problem:** Connection drops frequently
+- Use wired Ethernet instead of WiFi
+- Check network cable quality
+- Configure static IP addresses
+- Reduce network traffic on the subnet
+
+### Movement Issues
+
+**Problem:** Robot doesn't move
+- Check if motion is enabled: `arm.motion_enable(True)`
+- Verify robot state: `arm.get_state()`
+- Clear any errors: `arm.clean_error()`
+- Check if servo is attached: `arm.set_servo_attach()`
+
+**Problem:** Jerky or unexpected movements
+- Reduce speed and acceleration parameters
+- Check for network latency
+- Use `wait=True` for sequential movements
+- Verify trajectory planning settings
+
+### Python Environment Issues
+
+**Problem:** Module not found errors
+- Ensure virtual environment is activated
+- Reinstall xarm-python-sdk: `pip install --upgrade xarm-python-sdk`
+- Check Python version compatibility (3.8+)
+
+## Additional Resources
+
+### Official Documentation
+- [xArm Python SDK GitHub](https://github.com/xArm-Developer/xArm-Python-SDK)
+- [xArm API Documentation](https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md)
+- [UFACTORY Official Website](https://www.ufactory.cc/)
+
+### Community Resources
+- [AccelerationConsortium xarm-translocation](https://github.com/AccelerationConsortium/xarm-translocation) - Advanced control package
+- [xArm ROS Integration](https://github.com/xArm-Developer/xarm_ros) - For ROS users
+- [xArm ROS2 Integration](https://github.com/xArm-Developer/xarm_ros2) - For ROS2 users
+
+### AC Training Lab Resources
+- [AC Training Lab Documentation](https://ac-training-lab.readthedocs.io/)
+- [AC Training Lab GitHub](https://github.com/AccelerationConsortium/ac-training-lab)
+- [Discussions](https://github.com/AccelerationConsortium/ac-training-lab/discussions)
+
+## Contributing
+
+To contribute improvements or report issues:
+1. Open an issue on the [AC Training Lab GitHub](https://github.com/AccelerationConsortium/ac-training-lab/issues)
+2. Join the discussion at [AC Discussions](https://github.com/AccelerationConsortium/ac-training-lab/discussions)
+3. Submit a pull request with your changes
+
+## License
+
+This module is part of the ac-training-lab project and follows the same MIT License.
+
+## Support
+
+For support with:
+- **Hardware issues**: Contact UFACTORY support
+- **Software/integration issues**: Open an issue on GitHub
+- **General questions**: Join our discussions or reach out to sterling.baird@utoronto.ca
diff --git a/src/ac_training_lab/xarm5/__init__.py b/src/ac_training_lab/xarm5/__init__.py
new file mode 100644
index 00000000..f380c29d
--- /dev/null
+++ b/src/ac_training_lab/xarm5/__init__.py
@@ -0,0 +1,7 @@
+"""XArm5 control module for Raspberry Pi 5.
+
+This module provides integration for controlling the UFACTORY xArm5 robotic arm
+from a Raspberry Pi 5 as part of the AC Training Lab's rail/lift/arm combo system.
+"""
+
+__version__ = "0.1.0"
diff --git a/src/ac_training_lab/xarm5/_scripts/basic_movement.py b/src/ac_training_lab/xarm5/_scripts/basic_movement.py
new file mode 100644
index 00000000..0cbee376
--- /dev/null
+++ b/src/ac_training_lab/xarm5/_scripts/basic_movement.py
@@ -0,0 +1,118 @@
+"""Basic movement example for xArm5.
+
+This script demonstrates basic movement commands for the xArm5 robotic arm.
+Includes connection, enabling motion, moving to positions, and safe shutdown.
+"""
+
+from xarm.wrapper import XArmAPI
+import time
+
+# Configuration
+XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
+
+
+def main():
+ """Execute basic movement demonstration."""
+ print("=" * 60)
+ print("XArm5 Basic Movement Demo")
+ print("=" * 60)
+
+ # Connect to xArm5
+ print(f"\nConnecting to xArm5 at {XARM_IP}...")
+ arm = XArmAPI(XARM_IP)
+
+ if not arm.connected:
+ print("ā Failed to connect to xArm5")
+ return
+
+ print("ā Connected successfully")
+ print(f" Robot version: {arm.version}")
+ print(f" Robot state: {arm.state}")
+
+ try:
+ # Enable motion
+ print("\nEnabling motion...")
+ arm.motion_enable(enable=True)
+ arm.set_mode(0) # Position control mode
+ arm.set_state(0) # Ready state
+ time.sleep(1)
+
+ # Move to home position
+ print("\nMoving to home position...")
+ arm.move_gohome(wait=True)
+ print("ā Reached home position")
+ time.sleep(1)
+
+ # Get current position
+ position = arm.get_position()
+ print(f"\nCurrent position:")
+ print(f" X: {position[1][0]:.2f} mm")
+ print(f" Y: {position[1][1]:.2f} mm")
+ print(f" Z: {position[1][2]:.2f} mm")
+ print(f" Roll: {position[1][3]:.2f}°")
+ print(f" Pitch: {position[1][4]:.2f}°")
+ print(f" Yaw: {position[1][5]:.2f}°")
+
+ # Movement sequence
+ print("\nExecuting movement sequence...")
+
+ # Position 1
+ print("\n Moving to position 1 (forward)...")
+ arm.set_position(
+ x=300, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print(" ā Position 1 reached")
+ time.sleep(1)
+
+ # Position 2
+ print("\n Moving to position 2 (left)...")
+ arm.set_position(
+ x=300, y=100, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print(" ā Position 2 reached")
+ time.sleep(1)
+
+ # Position 3
+ print("\n Moving to position 3 (right)...")
+ arm.set_position(
+ x=300, y=-100, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print(" ā Position 3 reached")
+ time.sleep(1)
+
+ # Return to home
+ print("\nReturning to home position...")
+ arm.move_gohome(wait=True)
+ print("ā Returned to home")
+
+ print("\n" + "=" * 60)
+ print("Movement sequence completed successfully!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\nā ļø Interrupted by user - stopping robot...")
+ arm.emergency_stop()
+
+ except Exception as e:
+ print(f"\nā Error occurred: {e}")
+ print("Attempting emergency stop...")
+ arm.emergency_stop()
+
+ finally:
+ # Clean up
+ print("\nDisconnecting...")
+ arm.disconnect()
+ print("ā Disconnected safely")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/ac_training_lab/xarm5/_scripts/gripper_demo.py b/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
new file mode 100644
index 00000000..2c92bb71
--- /dev/null
+++ b/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
@@ -0,0 +1,207 @@
+"""Gripper control demonstration for xArm5.
+
+This script demonstrates how to control various gripper types with the xArm5.
+Supports standard gripper, BIO gripper, and RobotIQ gripper.
+"""
+
+from xarm.wrapper import XArmAPI
+import time
+
+# Configuration
+XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
+GRIPPER_TYPE = "standard" # Options: "standard", "bio", "robotiq"
+
+
+def demo_standard_gripper(arm):
+ """Demonstrate standard xArm gripper."""
+ print("\n" + "-" * 60)
+ print("Standard Gripper Demo")
+ print("-" * 60)
+
+ # Enable gripper
+ print("\nEnabling gripper...")
+ arm.set_gripper_enable(True)
+ arm.set_gripper_mode(0)
+ time.sleep(1)
+
+ # Get current gripper position
+ ret = arm.get_gripper_position()
+ if ret[0] == 0:
+ print(f"Current gripper position: {ret[1]} (0=closed, 800=open)")
+
+ # Open gripper
+ print("\nOpening gripper...")
+ arm.set_gripper_position(800, wait=True)
+ print("ā Gripper opened")
+ time.sleep(1)
+
+ # Partially close
+ print("\nPartially closing gripper (400/800)...")
+ arm.set_gripper_position(400, wait=True)
+ print("ā Gripper at 50% position")
+ time.sleep(1)
+
+ # Close gripper
+ print("\nClosing gripper...")
+ arm.set_gripper_position(0, wait=True)
+ print("ā Gripper closed")
+ time.sleep(1)
+
+ # Open again
+ print("\nOpening gripper again...")
+ arm.set_gripper_position(800, wait=True)
+ print("ā Gripper opened")
+
+
+def demo_bio_gripper(arm):
+ """Demonstrate BIO gripper."""
+ print("\n" + "-" * 60)
+ print("BIO Gripper Demo")
+ print("-" * 60)
+
+ # Enable BIO gripper
+ print("\nEnabling BIO gripper...")
+ arm.set_bio_gripper_enable(True)
+ time.sleep(1)
+
+ # Set speed
+ arm.set_bio_gripper_speed(500)
+
+ # Get status
+ ret = arm.get_bio_gripper_status()
+ if ret[0] == 0:
+ print(f"BIO Gripper status: {ret[1]}")
+
+ # Open gripper
+ print("\nOpening BIO gripper...")
+ arm.open_bio_gripper(wait=True)
+ print("ā BIO gripper opened")
+ time.sleep(1)
+
+ # Close gripper
+ print("\nClosing BIO gripper...")
+ arm.close_bio_gripper(wait=True)
+ print("ā BIO gripper closed")
+ time.sleep(1)
+
+ # Open again
+ print("\nOpening BIO gripper again...")
+ arm.open_bio_gripper(wait=True)
+ print("ā BIO gripper opened")
+
+
+def demo_robotiq_gripper(arm):
+ """Demonstrate RobotIQ gripper."""
+ print("\n" + "-" * 60)
+ print("RobotIQ Gripper Demo")
+ print("-" * 60)
+
+ # Reset and activate
+ print("\nInitializing RobotIQ gripper...")
+ arm.robotiq_reset()
+ time.sleep(2)
+ arm.robotiq_set_activate(True)
+ time.sleep(2)
+
+ # Get status
+ ret = arm.robotiq_get_status()
+ if ret[0] == 0:
+ print(f"RobotIQ status: {ret[1]}")
+
+ # Open gripper
+ print("\nOpening RobotIQ gripper...")
+ arm.robotiq_open(wait=True)
+ print("ā RobotIQ gripper opened")
+ time.sleep(1)
+
+ # Set specific position
+ print("\nSetting RobotIQ gripper to 50% (128/255)...")
+ arm.robotiq_set_position(128, wait=True)
+ print("ā RobotIQ gripper at 50% position")
+ time.sleep(1)
+
+ # Close gripper
+ print("\nClosing RobotIQ gripper...")
+ arm.robotiq_close(wait=True)
+ print("ā RobotIQ gripper closed")
+ time.sleep(1)
+
+ # Open again
+ print("\nOpening RobotIQ gripper again...")
+ arm.robotiq_open(wait=True)
+ print("ā RobotIQ gripper opened")
+
+
+def main():
+ """Execute gripper demonstration."""
+ print("=" * 60)
+ print("XArm5 Gripper Control Demo")
+ print(f"Gripper Type: {GRIPPER_TYPE.upper()}")
+ print("=" * 60)
+
+ # Connect to xArm5
+ print(f"\nConnecting to xArm5 at {XARM_IP}...")
+ arm = XArmAPI(XARM_IP)
+
+ if not arm.connected:
+ print("ā Failed to connect to xArm5")
+ return
+
+ print("ā Connected successfully")
+
+ try:
+ # Enable motion
+ print("\nEnabling motion...")
+ arm.motion_enable(enable=True)
+ arm.set_mode(0)
+ arm.set_state(0)
+ time.sleep(1)
+
+ # Move to working position
+ print("\nMoving to working position...")
+ arm.set_position(
+ x=300, y=0, z=300,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print("ā In working position")
+
+ # Execute gripper demo based on type
+ if GRIPPER_TYPE.lower() == "standard":
+ demo_standard_gripper(arm)
+ elif GRIPPER_TYPE.lower() == "bio":
+ demo_bio_gripper(arm)
+ elif GRIPPER_TYPE.lower() == "robotiq":
+ demo_robotiq_gripper(arm)
+ else:
+ print(f"\nā Unknown gripper type: {GRIPPER_TYPE}")
+ print(" Valid options: standard, bio, robotiq")
+
+ # Return to home
+ print("\nReturning to home position...")
+ arm.move_gohome(wait=True)
+ print("ā Returned to home")
+
+ print("\n" + "=" * 60)
+ print("Gripper demo completed successfully!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\nā ļø Interrupted by user - stopping robot...")
+ arm.emergency_stop()
+
+ except Exception as e:
+ print(f"\nā Error occurred: {e}")
+ print("Attempting emergency stop...")
+ arm.emergency_stop()
+
+ finally:
+ # Clean up
+ print("\nDisconnecting...")
+ arm.disconnect()
+ print("ā Disconnected safely")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/ac_training_lab/xarm5/_scripts/safety_demo.py b/src/ac_training_lab/xarm5/_scripts/safety_demo.py
new file mode 100644
index 00000000..82ebd25c
--- /dev/null
+++ b/src/ac_training_lab/xarm5/_scripts/safety_demo.py
@@ -0,0 +1,240 @@
+"""Safety features demonstration for xArm5.
+
+This script demonstrates safety features including emergency stop,
+error handling, collision detection, and safe workspace limits.
+"""
+
+from xarm.wrapper import XArmAPI
+import time
+
+# Configuration
+XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
+
+# Workspace limits (in mm)
+WORKSPACE_LIMITS = {
+ 'x_min': 100,
+ 'x_max': 500,
+ 'y_min': -300,
+ 'y_max': 300,
+ 'z_min': 50,
+ 'z_max': 500
+}
+
+
+def check_workspace_limits(position):
+ """Check if position is within safe workspace limits.
+
+ Args:
+ position: Tuple of (x, y, z) coordinates in mm
+
+ Returns:
+ bool: True if position is safe, False otherwise
+ """
+ x, y, z = position
+
+ if not (WORKSPACE_LIMITS['x_min'] <= x <= WORKSPACE_LIMITS['x_max']):
+ print(f"ā ļø X position {x} outside limits [{WORKSPACE_LIMITS['x_min']}, {WORKSPACE_LIMITS['x_max']}]")
+ return False
+
+ if not (WORKSPACE_LIMITS['y_min'] <= y <= WORKSPACE_LIMITS['y_max']):
+ print(f"ā ļø Y position {y} outside limits [{WORKSPACE_LIMITS['y_min']}, {WORKSPACE_LIMITS['y_max']}]")
+ return False
+
+ if not (WORKSPACE_LIMITS['z_min'] <= z <= WORKSPACE_LIMITS['z_max']):
+ print(f"ā ļø Z position {z} outside limits [{WORKSPACE_LIMITS['z_min']}, {WORKSPACE_LIMITS['z_max']}]")
+ return False
+
+ return True
+
+
+def demo_safe_movement(arm):
+ """Demonstrate safe movement with workspace limits."""
+ print("\n" + "-" * 60)
+ print("Safe Movement Demo")
+ print("-" * 60)
+
+ # Test safe position
+ safe_position = (300, 0, 200)
+ print(f"\nTesting safe position {safe_position}...")
+ if check_workspace_limits(safe_position):
+ print("ā Position is safe - executing movement")
+ arm.set_position(
+ x=safe_position[0],
+ y=safe_position[1],
+ z=safe_position[2],
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print("ā Movement completed")
+ time.sleep(1)
+ else:
+ print("ā Position rejected - outside workspace limits")
+
+ # Test unsafe position (too far)
+ unsafe_position = (700, 0, 200)
+ print(f"\nTesting unsafe position {unsafe_position}...")
+ if check_workspace_limits(unsafe_position):
+ print("ā Position is safe - would execute movement")
+ else:
+ print("ā Position rejected - outside workspace limits")
+ print(" Movement NOT executed for safety")
+
+
+def demo_error_handling(arm):
+ """Demonstrate error detection and recovery."""
+ print("\n" + "-" * 60)
+ print("Error Handling Demo")
+ print("-" * 60)
+
+ # Check for errors
+ err_code = arm.error_code
+ warn_code = arm.warn_code
+
+ print(f"\nCurrent error code: {err_code}")
+ print(f"Current warning code: {warn_code}")
+
+ if arm.has_error:
+ print("ā ļø Robot has errors - attempting to clear...")
+ arm.clean_error()
+ time.sleep(1)
+
+ if arm.has_error:
+ print("ā Failed to clear errors")
+ else:
+ print("ā Errors cleared successfully")
+ else:
+ print("ā No errors detected")
+
+ if arm.has_warn:
+ print("ā ļø Robot has warnings - attempting to clear...")
+ arm.clean_warn()
+ time.sleep(1)
+
+ if arm.has_warn:
+ print("ā Failed to clear warnings")
+ else:
+ print("ā Warnings cleared successfully")
+ else:
+ print("ā No warnings detected")
+
+
+def demo_collision_sensitivity(arm):
+ """Demonstrate collision sensitivity settings."""
+ print("\n" + "-" * 60)
+ print("Collision Sensitivity Demo")
+ print("-" * 60)
+
+ # Set collision sensitivity (0-5, where 0 is least sensitive)
+ sensitivity = 3
+ print(f"\nSetting collision sensitivity to {sensitivity}...")
+ arm.set_collision_sensitivity(sensitivity)
+ time.sleep(0.5)
+
+ print(f"ā Collision sensitivity set to {arm.collision_sensitivity}")
+ print(" Note: Higher values = more sensitive collision detection")
+
+
+def demo_emergency_stop(arm):
+ """Demonstrate emergency stop functionality."""
+ print("\n" + "-" * 60)
+ print("Emergency Stop Demo")
+ print("-" * 60)
+
+ print("\nStarting a slow movement...")
+ print("(In a real emergency, you would call arm.emergency_stop())")
+
+ # Start a movement
+ arm.set_position(
+ x=350, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=50, # Slow speed for demonstration
+ wait=False # Don't wait for completion
+ )
+
+ # Simulate waiting a bit
+ time.sleep(0.5)
+
+ # Emergency stop
+ print("\nā ļø EMERGENCY STOP triggered!")
+ arm.emergency_stop()
+ print("ā Robot stopped immediately")
+
+ # Recovery process
+ print("\nRecovering from emergency stop...")
+ time.sleep(1)
+
+ # Clear errors and reset state
+ arm.clean_error()
+ arm.set_state(0)
+ time.sleep(1)
+
+ print("ā Robot recovered and ready")
+
+
+def main():
+ """Execute safety features demonstration."""
+ print("=" * 60)
+ print("XArm5 Safety Features Demo")
+ print("=" * 60)
+
+ print("\nWorkspace Limits:")
+ for key, value in WORKSPACE_LIMITS.items():
+ print(f" {key}: {value} mm")
+
+ # Connect to xArm5
+ print(f"\nConnecting to xArm5 at {XARM_IP}...")
+ arm = XArmAPI(XARM_IP)
+
+ if not arm.connected:
+ print("ā Failed to connect to xArm5")
+ return
+
+ print("ā Connected successfully")
+
+ try:
+ # Enable motion
+ print("\nEnabling motion...")
+ arm.motion_enable(enable=True)
+ arm.set_mode(0)
+ arm.set_state(0)
+ time.sleep(1)
+
+ # Move to starting position
+ print("\nMoving to starting position...")
+ arm.move_gohome(wait=True)
+ print("ā At starting position")
+
+ # Run safety demonstrations
+ demo_error_handling(arm)
+ demo_collision_sensitivity(arm)
+ demo_safe_movement(arm)
+ demo_emergency_stop(arm)
+
+ # Return to home
+ print("\nReturning to home position...")
+ arm.move_gohome(wait=True)
+ print("ā Returned to home")
+
+ print("\n" + "=" * 60)
+ print("Safety demo completed successfully!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\nā ļø Interrupted by user - stopping robot...")
+ arm.emergency_stop()
+
+ except Exception as e:
+ print(f"\nā Error occurred: {e}")
+ print("Attempting emergency stop...")
+ arm.emergency_stop()
+
+ finally:
+ # Clean up
+ print("\nDisconnecting...")
+ arm.disconnect()
+ print("ā Disconnected safely")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/ac_training_lab/xarm5/_scripts/track_integration.py b/src/ac_training_lab/xarm5/_scripts/track_integration.py
new file mode 100644
index 00000000..1dc0c625
--- /dev/null
+++ b/src/ac_training_lab/xarm5/_scripts/track_integration.py
@@ -0,0 +1,197 @@
+"""Linear track integration demonstration for xArm5.
+
+This script demonstrates how to control the xArm5 with a linear track (rail)
+for extended workspace and coordinated movements.
+"""
+
+from xarm.wrapper import XArmAPI
+import time
+
+# Configuration
+XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
+TRACK_ENABLED = True # Set to False if no track is attached
+
+
+def main():
+ """Execute linear track integration demonstration."""
+ print("=" * 60)
+ print("XArm5 Linear Track Integration Demo")
+ print("=" * 60)
+
+ # Connect to xArm5 with track enabled
+ print(f"\nConnecting to xArm5 at {XARM_IP}...")
+ if TRACK_ENABLED:
+ print("Track mode: ENABLED")
+ arm = XArmAPI(XARM_IP, enable_track=True)
+ else:
+ print("Track mode: DISABLED")
+ arm = XArmAPI(XARM_IP)
+
+ if not arm.connected:
+ print("ā Failed to connect to xArm5")
+ return
+
+ print("ā Connected successfully")
+ print(f" Robot version: {arm.version}")
+
+ try:
+ # Enable motion
+ print("\nEnabling motion...")
+ arm.motion_enable(enable=True)
+ arm.set_mode(0)
+ arm.set_state(0)
+ time.sleep(1)
+
+ if TRACK_ENABLED:
+ # Linear track operations
+ print("\n" + "-" * 60)
+ print("Linear Track Control")
+ print("-" * 60)
+
+ # Enable linear motor
+ print("\nEnabling linear motor (track)...")
+ arm.set_linear_motor_enable(True)
+ time.sleep(1)
+
+ # Get track status
+ ret = arm.get_linear_motor_status()
+ print(f"Track status: {ret}")
+
+ # Get current track position
+ ret = arm.get_linear_motor_pos()
+ if ret[0] == 0:
+ current_pos = ret[1]
+ print(f"Current track position: {current_pos} mm")
+
+ # Move track to position 1 (0 mm - home)
+ print("\nMoving track to home position (0 mm)...")
+ arm.set_linear_motor_pos(0, wait=True)
+ print("ā Track at home position")
+ time.sleep(1)
+
+ # Verify position
+ ret = arm.get_linear_motor_pos()
+ if ret[0] == 0:
+ print(f" Confirmed position: {ret[1]} mm")
+
+ # Move track to position 2 (300 mm)
+ print("\nMoving track to position 300 mm...")
+ arm.set_linear_motor_pos(300, wait=True)
+ print("ā Track at 300 mm")
+ time.sleep(1)
+
+ # Verify position
+ ret = arm.get_linear_motor_pos()
+ if ret[0] == 0:
+ print(f" Confirmed position: {ret[1]} mm")
+
+ # Move track to position 3 (600 mm)
+ print("\nMoving track to position 600 mm...")
+ arm.set_linear_motor_pos(600, wait=True)
+ print("ā Track at 600 mm")
+ time.sleep(1)
+
+ # Verify position
+ ret = arm.get_linear_motor_pos()
+ if ret[0] == 0:
+ print(f" Confirmed position: {ret[1]} mm")
+
+ # Coordinated arm and track movement
+ print("\n" + "-" * 60)
+ print("Coordinated Arm + Track Movement")
+ print("-" * 60)
+
+ # Position 1: Track at 0, arm reaching forward
+ print("\nPosition 1: Track home, arm forward...")
+ arm.set_linear_motor_pos(0, wait=True)
+ arm.set_position(
+ x=400, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print("ā Position 1 reached")
+ time.sleep(1)
+
+ # Position 2: Track at 300, arm centered
+ print("\nPosition 2: Track 300mm, arm centered...")
+ arm.set_linear_motor_pos(300, wait=True)
+ arm.set_position(
+ x=300, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print("ā Position 2 reached")
+ time.sleep(1)
+
+ # Position 3: Track at 600, arm reaching back
+ print("\nPosition 3: Track 600mm, arm back...")
+ arm.set_linear_motor_pos(600, wait=True)
+ arm.set_position(
+ x=200, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ print("ā Position 3 reached")
+ time.sleep(1)
+
+ # Return track to home
+ print("\nReturning track to home position...")
+ arm.set_linear_motor_pos(0, wait=True)
+ print("ā Track at home")
+
+ else:
+ # Just arm movements without track
+ print("\n(Track disabled - performing arm-only movements)")
+
+ print("\nMoving to position 1...")
+ arm.set_position(
+ x=300, y=0, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ time.sleep(1)
+
+ print("\nMoving to position 2...")
+ arm.set_position(
+ x=300, y=100, z=200,
+ roll=180, pitch=0, yaw=0,
+ speed=100,
+ wait=True
+ )
+ time.sleep(1)
+
+ # Return arm to home
+ print("\nReturning arm to home position...")
+ arm.move_gohome(wait=True)
+ print("ā Arm at home")
+
+ print("\n" + "=" * 60)
+ print("Track integration demo completed successfully!")
+ print("=" * 60)
+
+ except KeyboardInterrupt:
+ print("\n\nā ļø Interrupted by user - stopping robot...")
+ arm.emergency_stop()
+ if TRACK_ENABLED:
+ arm.set_linear_motor_stop()
+
+ except Exception as e:
+ print(f"\nā Error occurred: {e}")
+ print("Attempting emergency stop...")
+ arm.emergency_stop()
+ if TRACK_ENABLED:
+ arm.set_linear_motor_stop()
+
+ finally:
+ # Clean up
+ print("\nDisconnecting...")
+ arm.disconnect()
+ print("ā Disconnected safely")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/ac_training_lab/xarm5/requirements.txt b/src/ac_training_lab/xarm5/requirements.txt
new file mode 100644
index 00000000..3e16de63
--- /dev/null
+++ b/src/ac_training_lab/xarm5/requirements.txt
@@ -0,0 +1,7 @@
+# XArm5 Raspberry Pi Control Requirements
+
+# Official UFACTORY xArm Python SDK
+xarm-python-sdk>=1.17.0
+
+# Core dependencies (usually included with SDK)
+numpy>=1.20.0
From 20f720228507256c4d889f2513acdb5980210a83 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 22:36:32 +0000
Subject: [PATCH 3/4] Fix linting issues in xarm5 module
Co-authored-by: sgbaird <45469701+sgbaird@users.noreply.github.com>
---
.../xarm5/_scripts/basic_movement.py | 50 ++++----
.../xarm5/_scripts/gripper_demo.py | 66 +++++-----
.../xarm5/_scripts/safety_demo.py | 119 ++++++++++--------
.../xarm5/_scripts/track_integration.py | 82 +++++-------
src/ac_training_lab/xarm5/requirements.txt | 4 +-
5 files changed, 155 insertions(+), 166 deletions(-)
diff --git a/src/ac_training_lab/xarm5/_scripts/basic_movement.py b/src/ac_training_lab/xarm5/_scripts/basic_movement.py
index 0cbee376..13920867 100644
--- a/src/ac_training_lab/xarm5/_scripts/basic_movement.py
+++ b/src/ac_training_lab/xarm5/_scripts/basic_movement.py
@@ -4,9 +4,10 @@
Includes connection, enabling motion, moving to positions, and safe shutdown.
"""
-from xarm.wrapper import XArmAPI
import time
+from xarm.wrapper import XArmAPI
+
# Configuration
XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
@@ -16,19 +17,19 @@ def main():
print("=" * 60)
print("XArm5 Basic Movement Demo")
print("=" * 60)
-
+
# Connect to xArm5
print(f"\nConnecting to xArm5 at {XARM_IP}...")
arm = XArmAPI(XARM_IP)
-
+
if not arm.connected:
print("ā Failed to connect to xArm5")
return
-
+
print("ā Connected successfully")
print(f" Robot version: {arm.version}")
print(f" Robot state: {arm.state}")
-
+
try:
# Enable motion
print("\nEnabling motion...")
@@ -36,77 +37,68 @@ def main():
arm.set_mode(0) # Position control mode
arm.set_state(0) # Ready state
time.sleep(1)
-
+
# Move to home position
print("\nMoving to home position...")
arm.move_gohome(wait=True)
print("ā Reached home position")
time.sleep(1)
-
+
# Get current position
position = arm.get_position()
- print(f"\nCurrent position:")
+ print("\nCurrent position:")
print(f" X: {position[1][0]:.2f} mm")
print(f" Y: {position[1][1]:.2f} mm")
print(f" Z: {position[1][2]:.2f} mm")
print(f" Roll: {position[1][3]:.2f}°")
print(f" Pitch: {position[1][4]:.2f}°")
print(f" Yaw: {position[1][5]:.2f}°")
-
+
# Movement sequence
print("\nExecuting movement sequence...")
-
+
# Position 1
print("\n Moving to position 1 (forward)...")
arm.set_position(
- x=300, y=0, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print(" ā Position 1 reached")
time.sleep(1)
-
+
# Position 2
print("\n Moving to position 2 (left)...")
arm.set_position(
- x=300, y=100, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print(" ā Position 2 reached")
time.sleep(1)
-
+
# Position 3
print("\n Moving to position 3 (right)...")
arm.set_position(
- x=300, y=-100, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=-100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print(" ā Position 3 reached")
time.sleep(1)
-
+
# Return to home
print("\nReturning to home position...")
arm.move_gohome(wait=True)
print("ā Returned to home")
-
+
print("\n" + "=" * 60)
print("Movement sequence completed successfully!")
print("=" * 60)
-
+
except KeyboardInterrupt:
print("\n\nā ļø Interrupted by user - stopping robot...")
arm.emergency_stop()
-
+
except Exception as e:
print(f"\nā Error occurred: {e}")
print("Attempting emergency stop...")
arm.emergency_stop()
-
+
finally:
# Clean up
print("\nDisconnecting...")
diff --git a/src/ac_training_lab/xarm5/_scripts/gripper_demo.py b/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
index 2c92bb71..601cfc2e 100644
--- a/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
+++ b/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
@@ -4,9 +4,10 @@
Supports standard gripper, BIO gripper, and RobotIQ gripper.
"""
-from xarm.wrapper import XArmAPI
import time
+from xarm.wrapper import XArmAPI
+
# Configuration
XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
GRIPPER_TYPE = "standard" # Options: "standard", "bio", "robotiq"
@@ -17,36 +18,36 @@ def demo_standard_gripper(arm):
print("\n" + "-" * 60)
print("Standard Gripper Demo")
print("-" * 60)
-
+
# Enable gripper
print("\nEnabling gripper...")
arm.set_gripper_enable(True)
arm.set_gripper_mode(0)
time.sleep(1)
-
+
# Get current gripper position
ret = arm.get_gripper_position()
if ret[0] == 0:
print(f"Current gripper position: {ret[1]} (0=closed, 800=open)")
-
+
# Open gripper
print("\nOpening gripper...")
arm.set_gripper_position(800, wait=True)
print("ā Gripper opened")
time.sleep(1)
-
+
# Partially close
print("\nPartially closing gripper (400/800)...")
arm.set_gripper_position(400, wait=True)
print("ā Gripper at 50% position")
time.sleep(1)
-
+
# Close gripper
print("\nClosing gripper...")
arm.set_gripper_position(0, wait=True)
print("ā Gripper closed")
time.sleep(1)
-
+
# Open again
print("\nOpening gripper again...")
arm.set_gripper_position(800, wait=True)
@@ -58,32 +59,32 @@ def demo_bio_gripper(arm):
print("\n" + "-" * 60)
print("BIO Gripper Demo")
print("-" * 60)
-
+
# Enable BIO gripper
print("\nEnabling BIO gripper...")
arm.set_bio_gripper_enable(True)
time.sleep(1)
-
+
# Set speed
arm.set_bio_gripper_speed(500)
-
+
# Get status
ret = arm.get_bio_gripper_status()
if ret[0] == 0:
print(f"BIO Gripper status: {ret[1]}")
-
+
# Open gripper
print("\nOpening BIO gripper...")
arm.open_bio_gripper(wait=True)
print("ā BIO gripper opened")
time.sleep(1)
-
+
# Close gripper
print("\nClosing BIO gripper...")
arm.close_bio_gripper(wait=True)
print("ā BIO gripper closed")
time.sleep(1)
-
+
# Open again
print("\nOpening BIO gripper again...")
arm.open_bio_gripper(wait=True)
@@ -95,37 +96,37 @@ def demo_robotiq_gripper(arm):
print("\n" + "-" * 60)
print("RobotIQ Gripper Demo")
print("-" * 60)
-
+
# Reset and activate
print("\nInitializing RobotIQ gripper...")
arm.robotiq_reset()
time.sleep(2)
arm.robotiq_set_activate(True)
time.sleep(2)
-
+
# Get status
ret = arm.robotiq_get_status()
if ret[0] == 0:
print(f"RobotIQ status: {ret[1]}")
-
+
# Open gripper
print("\nOpening RobotIQ gripper...")
arm.robotiq_open(wait=True)
print("ā RobotIQ gripper opened")
time.sleep(1)
-
+
# Set specific position
print("\nSetting RobotIQ gripper to 50% (128/255)...")
arm.robotiq_set_position(128, wait=True)
print("ā RobotIQ gripper at 50% position")
time.sleep(1)
-
+
# Close gripper
print("\nClosing RobotIQ gripper...")
arm.robotiq_close(wait=True)
print("ā RobotIQ gripper closed")
time.sleep(1)
-
+
# Open again
print("\nOpening RobotIQ gripper again...")
arm.robotiq_open(wait=True)
@@ -138,17 +139,17 @@ def main():
print("XArm5 Gripper Control Demo")
print(f"Gripper Type: {GRIPPER_TYPE.upper()}")
print("=" * 60)
-
+
# Connect to xArm5
print(f"\nConnecting to xArm5 at {XARM_IP}...")
arm = XArmAPI(XARM_IP)
-
+
if not arm.connected:
print("ā Failed to connect to xArm5")
return
-
+
print("ā Connected successfully")
-
+
try:
# Enable motion
print("\nEnabling motion...")
@@ -156,17 +157,14 @@ def main():
arm.set_mode(0)
arm.set_state(0)
time.sleep(1)
-
+
# Move to working position
print("\nMoving to working position...")
arm.set_position(
- x=300, y=0, z=300,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=0, z=300, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print("ā In working position")
-
+
# Execute gripper demo based on type
if GRIPPER_TYPE.lower() == "standard":
demo_standard_gripper(arm)
@@ -177,25 +175,25 @@ def main():
else:
print(f"\nā Unknown gripper type: {GRIPPER_TYPE}")
print(" Valid options: standard, bio, robotiq")
-
+
# Return to home
print("\nReturning to home position...")
arm.move_gohome(wait=True)
print("ā Returned to home")
-
+
print("\n" + "=" * 60)
print("Gripper demo completed successfully!")
print("=" * 60)
-
+
except KeyboardInterrupt:
print("\n\nā ļø Interrupted by user - stopping robot...")
arm.emergency_stop()
-
+
except Exception as e:
print(f"\nā Error occurred: {e}")
print("Attempting emergency stop...")
arm.emergency_stop()
-
+
finally:
# Clean up
print("\nDisconnecting...")
diff --git a/src/ac_training_lab/xarm5/_scripts/safety_demo.py b/src/ac_training_lab/xarm5/_scripts/safety_demo.py
index 82ebd25c..89f8159e 100644
--- a/src/ac_training_lab/xarm5/_scripts/safety_demo.py
+++ b/src/ac_training_lab/xarm5/_scripts/safety_demo.py
@@ -4,46 +4,53 @@
error handling, collision detection, and safe workspace limits.
"""
-from xarm.wrapper import XArmAPI
import time
+from xarm.wrapper import XArmAPI
+
# Configuration
XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
# Workspace limits (in mm)
WORKSPACE_LIMITS = {
- 'x_min': 100,
- 'x_max': 500,
- 'y_min': -300,
- 'y_max': 300,
- 'z_min': 50,
- 'z_max': 500
+ "x_min": 100,
+ "x_max": 500,
+ "y_min": -300,
+ "y_max": 300,
+ "z_min": 50,
+ "z_max": 500,
}
def check_workspace_limits(position):
"""Check if position is within safe workspace limits.
-
+
Args:
position: Tuple of (x, y, z) coordinates in mm
-
+
Returns:
bool: True if position is safe, False otherwise
"""
x, y, z = position
-
- if not (WORKSPACE_LIMITS['x_min'] <= x <= WORKSPACE_LIMITS['x_max']):
- print(f"ā ļø X position {x} outside limits [{WORKSPACE_LIMITS['x_min']}, {WORKSPACE_LIMITS['x_max']}]")
+
+ if not (WORKSPACE_LIMITS["x_min"] <= x <= WORKSPACE_LIMITS["x_max"]):
+ x_min = WORKSPACE_LIMITS["x_min"]
+ x_max = WORKSPACE_LIMITS["x_max"]
+ print(f"ā ļø X position {x} outside limits [{x_min}, {x_max}]")
return False
-
- if not (WORKSPACE_LIMITS['y_min'] <= y <= WORKSPACE_LIMITS['y_max']):
- print(f"ā ļø Y position {y} outside limits [{WORKSPACE_LIMITS['y_min']}, {WORKSPACE_LIMITS['y_max']}]")
+
+ if not (WORKSPACE_LIMITS["y_min"] <= y <= WORKSPACE_LIMITS["y_max"]):
+ y_min = WORKSPACE_LIMITS["y_min"]
+ y_max = WORKSPACE_LIMITS["y_max"]
+ print(f"ā ļø Y position {y} outside limits [{y_min}, {y_max}]")
return False
-
- if not (WORKSPACE_LIMITS['z_min'] <= z <= WORKSPACE_LIMITS['z_max']):
- print(f"ā ļø Z position {z} outside limits [{WORKSPACE_LIMITS['z_min']}, {WORKSPACE_LIMITS['z_max']}]")
+
+ if not (WORKSPACE_LIMITS["z_min"] <= z <= WORKSPACE_LIMITS["z_max"]):
+ z_min = WORKSPACE_LIMITS["z_min"]
+ z_max = WORKSPACE_LIMITS["z_max"]
+ print(f"ā ļø Z position {z} outside limits [{z_min}, {z_max}]")
return False
-
+
return True
@@ -52,7 +59,7 @@ def demo_safe_movement(arm):
print("\n" + "-" * 60)
print("Safe Movement Demo")
print("-" * 60)
-
+
# Test safe position
safe_position = (300, 0, 200)
print(f"\nTesting safe position {safe_position}...")
@@ -62,15 +69,17 @@ def demo_safe_movement(arm):
x=safe_position[0],
y=safe_position[1],
z=safe_position[2],
- roll=180, pitch=0, yaw=0,
+ roll=180,
+ pitch=0,
+ yaw=0,
speed=100,
- wait=True
+ wait=True,
)
print("ā Movement completed")
time.sleep(1)
else:
print("ā Position rejected - outside workspace limits")
-
+
# Test unsafe position (too far)
unsafe_position = (700, 0, 200)
print(f"\nTesting unsafe position {unsafe_position}...")
@@ -86,31 +95,31 @@ def demo_error_handling(arm):
print("\n" + "-" * 60)
print("Error Handling Demo")
print("-" * 60)
-
+
# Check for errors
err_code = arm.error_code
warn_code = arm.warn_code
-
+
print(f"\nCurrent error code: {err_code}")
print(f"Current warning code: {warn_code}")
-
+
if arm.has_error:
print("ā ļø Robot has errors - attempting to clear...")
arm.clean_error()
time.sleep(1)
-
+
if arm.has_error:
print("ā Failed to clear errors")
else:
print("ā Errors cleared successfully")
else:
print("ā No errors detected")
-
+
if arm.has_warn:
print("ā ļø Robot has warnings - attempting to clear...")
arm.clean_warn()
time.sleep(1)
-
+
if arm.has_warn:
print("ā Failed to clear warnings")
else:
@@ -124,13 +133,13 @@ def demo_collision_sensitivity(arm):
print("\n" + "-" * 60)
print("Collision Sensitivity Demo")
print("-" * 60)
-
+
# Set collision sensitivity (0-5, where 0 is least sensitive)
sensitivity = 3
print(f"\nSetting collision sensitivity to {sensitivity}...")
arm.set_collision_sensitivity(sensitivity)
time.sleep(0.5)
-
+
print(f"ā Collision sensitivity set to {arm.collision_sensitivity}")
print(" Note: Higher values = more sensitive collision detection")
@@ -140,35 +149,39 @@ def demo_emergency_stop(arm):
print("\n" + "-" * 60)
print("Emergency Stop Demo")
print("-" * 60)
-
+
print("\nStarting a slow movement...")
print("(In a real emergency, you would call arm.emergency_stop())")
-
+
# Start a movement
arm.set_position(
- x=350, y=0, z=200,
- roll=180, pitch=0, yaw=0,
+ x=350,
+ y=0,
+ z=200,
+ roll=180,
+ pitch=0,
+ yaw=0,
speed=50, # Slow speed for demonstration
- wait=False # Don't wait for completion
+ wait=False, # Don't wait for completion
)
-
+
# Simulate waiting a bit
time.sleep(0.5)
-
+
# Emergency stop
print("\nā ļø EMERGENCY STOP triggered!")
arm.emergency_stop()
print("ā Robot stopped immediately")
-
+
# Recovery process
print("\nRecovering from emergency stop...")
time.sleep(1)
-
+
# Clear errors and reset state
arm.clean_error()
arm.set_state(0)
time.sleep(1)
-
+
print("ā Robot recovered and ready")
@@ -177,21 +190,21 @@ def main():
print("=" * 60)
print("XArm5 Safety Features Demo")
print("=" * 60)
-
+
print("\nWorkspace Limits:")
for key, value in WORKSPACE_LIMITS.items():
print(f" {key}: {value} mm")
-
+
# Connect to xArm5
print(f"\nConnecting to xArm5 at {XARM_IP}...")
arm = XArmAPI(XARM_IP)
-
+
if not arm.connected:
print("ā Failed to connect to xArm5")
return
-
+
print("ā Connected successfully")
-
+
try:
# Enable motion
print("\nEnabling motion...")
@@ -199,36 +212,36 @@ def main():
arm.set_mode(0)
arm.set_state(0)
time.sleep(1)
-
+
# Move to starting position
print("\nMoving to starting position...")
arm.move_gohome(wait=True)
print("ā At starting position")
-
+
# Run safety demonstrations
demo_error_handling(arm)
demo_collision_sensitivity(arm)
demo_safe_movement(arm)
demo_emergency_stop(arm)
-
+
# Return to home
print("\nReturning to home position...")
arm.move_gohome(wait=True)
print("ā Returned to home")
-
+
print("\n" + "=" * 60)
print("Safety demo completed successfully!")
print("=" * 60)
-
+
except KeyboardInterrupt:
print("\n\nā ļø Interrupted by user - stopping robot...")
arm.emergency_stop()
-
+
except Exception as e:
print(f"\nā Error occurred: {e}")
print("Attempting emergency stop...")
arm.emergency_stop()
-
+
finally:
# Clean up
print("\nDisconnecting...")
diff --git a/src/ac_training_lab/xarm5/_scripts/track_integration.py b/src/ac_training_lab/xarm5/_scripts/track_integration.py
index 1dc0c625..d7cbba37 100644
--- a/src/ac_training_lab/xarm5/_scripts/track_integration.py
+++ b/src/ac_training_lab/xarm5/_scripts/track_integration.py
@@ -4,9 +4,10 @@
for extended workspace and coordinated movements.
"""
-from xarm.wrapper import XArmAPI
import time
+from xarm.wrapper import XArmAPI
+
# Configuration
XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
TRACK_ENABLED = True # Set to False if no track is attached
@@ -17,7 +18,7 @@ def main():
print("=" * 60)
print("XArm5 Linear Track Integration Demo")
print("=" * 60)
-
+
# Connect to xArm5 with track enabled
print(f"\nConnecting to xArm5 at {XARM_IP}...")
if TRACK_ENABLED:
@@ -26,14 +27,14 @@ def main():
else:
print("Track mode: DISABLED")
arm = XArmAPI(XARM_IP)
-
+
if not arm.connected:
print("ā Failed to connect to xArm5")
return
-
+
print("ā Connected successfully")
print(f" Robot version: {arm.version}")
-
+
try:
# Enable motion
print("\nEnabling motion...")
@@ -41,151 +42,136 @@ def main():
arm.set_mode(0)
arm.set_state(0)
time.sleep(1)
-
+
if TRACK_ENABLED:
# Linear track operations
print("\n" + "-" * 60)
print("Linear Track Control")
print("-" * 60)
-
+
# Enable linear motor
print("\nEnabling linear motor (track)...")
arm.set_linear_motor_enable(True)
time.sleep(1)
-
+
# Get track status
ret = arm.get_linear_motor_status()
print(f"Track status: {ret}")
-
+
# Get current track position
ret = arm.get_linear_motor_pos()
if ret[0] == 0:
current_pos = ret[1]
print(f"Current track position: {current_pos} mm")
-
+
# Move track to position 1 (0 mm - home)
print("\nMoving track to home position (0 mm)...")
arm.set_linear_motor_pos(0, wait=True)
print("ā Track at home position")
time.sleep(1)
-
+
# Verify position
ret = arm.get_linear_motor_pos()
if ret[0] == 0:
print(f" Confirmed position: {ret[1]} mm")
-
+
# Move track to position 2 (300 mm)
print("\nMoving track to position 300 mm...")
arm.set_linear_motor_pos(300, wait=True)
print("ā Track at 300 mm")
time.sleep(1)
-
+
# Verify position
ret = arm.get_linear_motor_pos()
if ret[0] == 0:
print(f" Confirmed position: {ret[1]} mm")
-
+
# Move track to position 3 (600 mm)
print("\nMoving track to position 600 mm...")
arm.set_linear_motor_pos(600, wait=True)
print("ā Track at 600 mm")
time.sleep(1)
-
+
# Verify position
ret = arm.get_linear_motor_pos()
if ret[0] == 0:
print(f" Confirmed position: {ret[1]} mm")
-
+
# Coordinated arm and track movement
print("\n" + "-" * 60)
print("Coordinated Arm + Track Movement")
print("-" * 60)
-
+
# Position 1: Track at 0, arm reaching forward
print("\nPosition 1: Track home, arm forward...")
arm.set_linear_motor_pos(0, wait=True)
arm.set_position(
- x=400, y=0, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=400, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print("ā Position 1 reached")
time.sleep(1)
-
+
# Position 2: Track at 300, arm centered
print("\nPosition 2: Track 300mm, arm centered...")
arm.set_linear_motor_pos(300, wait=True)
arm.set_position(
- x=300, y=0, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print("ā Position 2 reached")
time.sleep(1)
-
+
# Position 3: Track at 600, arm reaching back
print("\nPosition 3: Track 600mm, arm back...")
arm.set_linear_motor_pos(600, wait=True)
arm.set_position(
- x=200, y=0, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=200, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
print("ā Position 3 reached")
time.sleep(1)
-
+
# Return track to home
print("\nReturning track to home position...")
arm.set_linear_motor_pos(0, wait=True)
print("ā Track at home")
-
+
else:
# Just arm movements without track
print("\n(Track disabled - performing arm-only movements)")
-
+
print("\nMoving to position 1...")
arm.set_position(
- x=300, y=0, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
time.sleep(1)
-
+
print("\nMoving to position 2...")
arm.set_position(
- x=300, y=100, z=200,
- roll=180, pitch=0, yaw=0,
- speed=100,
- wait=True
+ x=300, y=100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
)
time.sleep(1)
-
+
# Return arm to home
print("\nReturning arm to home position...")
arm.move_gohome(wait=True)
print("ā Arm at home")
-
+
print("\n" + "=" * 60)
print("Track integration demo completed successfully!")
print("=" * 60)
-
+
except KeyboardInterrupt:
print("\n\nā ļø Interrupted by user - stopping robot...")
arm.emergency_stop()
if TRACK_ENABLED:
arm.set_linear_motor_stop()
-
+
except Exception as e:
print(f"\nā Error occurred: {e}")
print("Attempting emergency stop...")
arm.emergency_stop()
if TRACK_ENABLED:
arm.set_linear_motor_stop()
-
+
finally:
# Clean up
print("\nDisconnecting...")
diff --git a/src/ac_training_lab/xarm5/requirements.txt b/src/ac_training_lab/xarm5/requirements.txt
index 3e16de63..c6c9a58f 100644
--- a/src/ac_training_lab/xarm5/requirements.txt
+++ b/src/ac_training_lab/xarm5/requirements.txt
@@ -1,7 +1,7 @@
# XArm5 Raspberry Pi Control Requirements
-# Official UFACTORY xArm Python SDK
-xarm-python-sdk>=1.17.0
# Core dependencies (usually included with SDK)
numpy>=1.20.0
+# Official UFACTORY xArm Python SDK
+xarm-python-sdk>=1.17.0
From 25ccd346f5acd554a9f481ea8d06883f98234a72 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 03:19:40 +0000
Subject: [PATCH 4/4] Revert extensive xArm5 module, replace with minimal
starter example
Co-authored-by: sgbaird <45469701+sgbaird@users.noreply.github.com>
---
CHANGELOG.md | 7 -
docs/_static/images/README_xarm5.txt | 12 -
docs/index.md | 1 -
setup.cfg | 3 -
src/ac_training_lab/xarm5/README.md | 290 ------------------
src/ac_training_lab/xarm5/__init__.py | 7 -
.../xarm5/_scripts/basic_movement.py | 110 -------
.../xarm5/_scripts/gripper_demo.py | 205 -------------
.../xarm5/_scripts/safety_demo.py | 253 ---------------
.../xarm5/_scripts/track_integration.py | 183 -----------
src/ac_training_lab/xarm5/requirements.txt | 7 -
src/ac_training_lab/xarm5_minimal_example.py | 39 +++
12 files changed, 39 insertions(+), 1078 deletions(-)
delete mode 100644 docs/_static/images/README_xarm5.txt
delete mode 100644 src/ac_training_lab/xarm5/README.md
delete mode 100644 src/ac_training_lab/xarm5/__init__.py
delete mode 100644 src/ac_training_lab/xarm5/_scripts/basic_movement.py
delete mode 100644 src/ac_training_lab/xarm5/_scripts/gripper_demo.py
delete mode 100644 src/ac_training_lab/xarm5/_scripts/safety_demo.py
delete mode 100644 src/ac_training_lab/xarm5/_scripts/track_integration.py
delete mode 100644 src/ac_training_lab/xarm5/requirements.txt
create mode 100644 src/ac_training_lab/xarm5_minimal_example.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 241ae4c4..db43d22d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,6 @@
## [Unreleased]
### Added
-- **XArm5 Integration**: Complete module for controlling UFACTORY xArm5 robotic arm from Raspberry Pi 5 (`src/ac_training_lab/xarm5/`)
- - Comprehensive README with setup instructions for Raspberry Pi 5
- - Requirements file with xarm-python-sdk dependency
- - Example scripts for basic movement, gripper control, linear track integration, and safety features
- - Support for rail/lift/arm combo integration with linear track control
- - Network configuration guide for xArm5 control box
- - Troubleshooting and safety documentation
- Support for both `rpicam-vid` (Raspberry Pi OS Trixie) and `libcamera-vid` (Raspberry Pi OS Bookworm) camera commands in `src/ac_training_lab/picam/device.py` to ensure compatibility across different OS versions.
- Comprehensive Unit Operations section in `docs/index.md` documenting all available capabilities including dispensing, synthesis, characterization, and robotics operations.
- Expanded Training Workflows section in `docs/index.md` with 10 educational workflows including RGB/RYB color matching, titration, yeast growth optimization, vision-enabled 3D printing optimization, microscopy image stitching, and AprilTag robot path planning.
diff --git a/docs/_static/images/README_xarm5.txt b/docs/_static/images/README_xarm5.txt
deleted file mode 100644
index 8ce43acb..00000000
--- a/docs/_static/images/README_xarm5.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-Note: An image file "ufactory-xarm5.png" needs to be added to this directory.
-
-The image should be:
-- A photo or render of the UFACTORY xArm5 robotic arm
-- Approximately 50px height (width auto-scaled)
-- PNG or WebP format
-- Can be sourced from:
- - UFACTORY official website: https://www.ufactory.cc/product-page/ufactory-xarm-5
- - Product documentation
- - Community resources
-
-Until the image is added, the docs/index.md table entry references this image file.
diff --git a/docs/index.md b/docs/index.md
index 723a95ee..f28aaa3b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -50,7 +50,6 @@ Here are some of the modules we have procured and are in the process of setting
| [Hiwonder ArmPi FPV](https://www.hiwonder.com/products/armpi?_pos=4&_sid=a9741a308&_ss=r) |
| 1 | An educational six-axis robotic arm | |
| [Hiwonder JetAuto Pro](https://www.hiwonder.com/products/jetauto-pro?variant=40040875229271) |
| 1 | An educational six-axis mobile cobot with a 3D depth camera and lidar | |
| [MyCobot 280](https://shop.elephantrobotics.com/en-ca/collections/mycobot/products/mycobot-pi-worlds-smallest-and-lightest-six-axis-collaborative-robot) and [MyAGV](https://shop.elephantrobotics.com/en-ca/collections/myagv/products/myagv-2023-pi?variant=47262714069304) |
| 1 | An educational six-axis mobile cobot | |
-| [UFACTORY xArm5](https://www.ufactory.cc/product-page/ufactory-xarm-5) ([docs](../src/ac_training_lab/xarm5/)) | 𦾠| 1 | A 5-DOF collaborative robotic arm controlled from Raspberry Pi 5. Supports gripper, linear track integration, and network-based control for rail/lift/arm combo systems. | |
| [AutoTrickler v4](https://autotrickler.com/pages/autotrickler-v4) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
| [ChargeMaster Supreme](https://www.rcbs.com/priming-and-powder-charging/powder-dispensers-and-scales/chargemaster-supreme-electronic-powder-dispenser/16-98943.html) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
| [Ingenuity Powder System](https://ingenuityprecision.com/product/ingenuity-powder-system/) |
| 1 | An automated solid dispensing station, usually marketed for ammunition reloading, but to be used as a general-purpose powder doser | |
diff --git a/setup.cfg b/setup.cfg
index 46e23589..1e2902a2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -74,9 +74,6 @@ a1-cam =
boto3
wget
-xarm5 =
- xarm-python-sdk>=1.17.0
-
[options.entry_points]
# Add here console scripts like:
# console_scripts =
diff --git a/src/ac_training_lab/xarm5/README.md b/src/ac_training_lab/xarm5/README.md
deleted file mode 100644
index bfd26bfa..00000000
--- a/src/ac_training_lab/xarm5/README.md
+++ /dev/null
@@ -1,290 +0,0 @@
-# XArm5 Control for Raspberry Pi 5
-
-This module provides integration for controlling the UFACTORY xArm5 robotic arm from a Raspberry Pi 5 as part of the AC Training Lab's rail/lift/arm combo system.
-
-## Hardware Overview
-
-The **xArm5** is a 5-degree-of-freedom collaborative robotic arm manufactured by UFACTORY. It features:
-- 5 servo joints with precise control
-- Network-based control via TCP/IP
-- Support for various end effectors (grippers, sensors)
-- Compatible with linear tracks and additional accessories
-- Payload capacity suitable for laboratory automation tasks
-
-## Prerequisites
-
-### Hardware Requirements
-- Raspberry Pi 5 (4GB or 8GB RAM recommended)
-- UFACTORY xArm5 robotic arm with control box
-- Ethernet connection (recommended) or WiFi
-- Power supply for both Raspberry Pi and xArm5
-- Optional: Linear track for extended workspace
-- Optional: Gripper or end effector
-
-### Software Requirements
-- Raspberry Pi OS (Bookworm or later)
-- Python 3.8 or higher
-- Network connectivity between Raspberry Pi and xArm5 control box
-
-## Installation on Raspberry Pi 5
-
-### 1. Set Up Raspberry Pi OS
-
-If starting fresh, use the [Raspberry Pi Imager](https://www.raspberrypi.org/software/) to install Raspberry Pi OS on a microSD card.
-
-```bash
-# Update system packages
-sudo apt update
-sudo apt upgrade -y
-```
-
-### 2. Install Python Dependencies
-
-```bash
-# Install Python virtual environment tools
-sudo apt install python3-pip python3-venv -y
-
-# Create a virtual environment (recommended for Bookworm OS)
-python3 -m venv ~/xarm-env
-source ~/xarm-env/bin/activate
-```
-
-### 3. Install xArm Python SDK
-
-**Option A: Install from PyPI (Recommended)**
-```bash
-pip install xarm-python-sdk
-```
-
-**Option B: Install from source**
-```bash
-git clone https://github.com/xArm-Developer/xArm-Python-SDK.git
-cd xArm-Python-SDK
-pip install .
-```
-
-### 4. Install AC Training Lab Package (Optional)
-
-To use this module as part of the ac-training-lab package:
-```bash
-# Clone the ac-training-lab repository
-git clone https://github.com/AccelerationConsortium/ac-dev-lab.git
-cd ac-dev-lab
-
-# Install with xarm5 dependencies
-pip install -e .
-```
-
-## Network Configuration
-
-### Finding Your xArm5 IP Address
-
-1. Check the xArm5 control box display panel
-2. Or check your router's DHCP client list
-3. Or use network scanning tools:
- ```bash
- sudo apt install nmap
- nmap -sn 192.168.1.0/24 # Adjust subnet to match your network
- ```
-
-### Static IP Configuration (Recommended)
-
-For reliable automation, configure a static IP for the xArm5 control box:
-1. Access the control box web interface
-2. Navigate to Network Settings
-3. Set a static IP address (e.g., 192.168.1.100)
-4. Configure gateway and DNS if needed
-
-## Quick Start Examples
-
-### Basic Connection Test
-
-```python
-from xarm.wrapper import XArmAPI
-
-# Replace with your xArm5 control box IP
-arm = XArmAPI('192.168.1.100')
-
-# Check connection and get robot info
-print(f"Connected: {arm.connected}")
-print(f"Version: {arm.version}")
-print(f"State: {arm.state}")
-
-# Disconnect
-arm.disconnect()
-```
-
-### Simple Movement Example
-
-```python
-from xarm.wrapper import XArmAPI
-
-# Connect to xArm5
-arm = XArmAPI('192.168.1.100')
-
-# Enable motion
-arm.motion_enable(enable=True)
-arm.set_mode(0) # Position control mode
-arm.set_state(0) # Ready state
-
-# Move to home position
-arm.move_gohome(wait=True)
-
-# Move to a specific position (x, y, z in mm, angles in degrees)
-arm.set_position(x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True)
-
-# Get current position
-position = arm.get_position()
-print(f"Current position: {position}")
-
-# Disconnect
-arm.disconnect()
-```
-
-### Using with Gripper
-
-```python
-from xarm.wrapper import XArmAPI
-
-# Connect
-arm = XArmAPI('192.168.1.100')
-
-# Enable gripper
-arm.set_gripper_enable(True)
-arm.set_gripper_mode(0)
-
-# Open gripper
-arm.set_gripper_position(800, wait=True) # 800 = fully open
-
-# Close gripper
-arm.set_gripper_position(0, wait=True) # 0 = fully closed
-
-# Disconnect
-arm.disconnect()
-```
-
-## Rail/Lift/Arm Integration
-
-For integration with linear tracks and vertical lifts:
-
-### Linear Track Control
-
-```python
-from xarm.wrapper import XArmAPI
-
-arm = XArmAPI('192.168.1.100', enable_track=True)
-
-# Enable linear motor (track)
-arm.set_linear_motor_enable(True)
-
-# Move track to specific position (in mm)
-arm.set_linear_motor_pos(500, wait=True)
-
-# Get track position
-track_pos = arm.get_linear_motor_pos()
-print(f"Track position: {track_pos} mm")
-```
-
-## Example Scripts
-
-See the `_scripts/` directory for more examples:
-- `basic_movement.py` - Basic arm movements
-- `gripper_demo.py` - Gripper control demonstrations
-- `track_integration.py` - Linear track coordination
-- `safety_demo.py` - Emergency stop and safety features
-
-## Safety Considerations
-
-ā ļø **IMPORTANT SAFETY WARNINGS:**
-
-1. **Keep clear of the robot workspace** during operation
-2. **Test in simulation first** before running on real hardware
-3. **Use emergency stop** features in your code
-4. **Start with slow speeds** when testing new movements
-5. **Monitor the robot** during initial testing
-6. **Set appropriate workspace limits** to prevent collisions
-7. **Enable collision detection** when available
-
-### Emergency Stop
-
-```python
-# In case of emergency
-arm.emergency_stop()
-
-# Reset after emergency stop
-arm.clean_error()
-arm.set_state(0)
-```
-
-## Troubleshooting
-
-### Connection Issues
-
-**Problem:** Cannot connect to xArm5
-- Verify both devices are on the same network
-- Check IP address is correct
-- Ping the control box: `ping 192.168.1.100`
-- Check firewall settings on Raspberry Pi
-- Ensure control box is powered on and initialized
-
-**Problem:** Connection drops frequently
-- Use wired Ethernet instead of WiFi
-- Check network cable quality
-- Configure static IP addresses
-- Reduce network traffic on the subnet
-
-### Movement Issues
-
-**Problem:** Robot doesn't move
-- Check if motion is enabled: `arm.motion_enable(True)`
-- Verify robot state: `arm.get_state()`
-- Clear any errors: `arm.clean_error()`
-- Check if servo is attached: `arm.set_servo_attach()`
-
-**Problem:** Jerky or unexpected movements
-- Reduce speed and acceleration parameters
-- Check for network latency
-- Use `wait=True` for sequential movements
-- Verify trajectory planning settings
-
-### Python Environment Issues
-
-**Problem:** Module not found errors
-- Ensure virtual environment is activated
-- Reinstall xarm-python-sdk: `pip install --upgrade xarm-python-sdk`
-- Check Python version compatibility (3.8+)
-
-## Additional Resources
-
-### Official Documentation
-- [xArm Python SDK GitHub](https://github.com/xArm-Developer/xArm-Python-SDK)
-- [xArm API Documentation](https://github.com/xArm-Developer/xArm-Python-SDK/blob/master/doc/api/xarm_api.md)
-- [UFACTORY Official Website](https://www.ufactory.cc/)
-
-### Community Resources
-- [AccelerationConsortium xarm-translocation](https://github.com/AccelerationConsortium/xarm-translocation) - Advanced control package
-- [xArm ROS Integration](https://github.com/xArm-Developer/xarm_ros) - For ROS users
-- [xArm ROS2 Integration](https://github.com/xArm-Developer/xarm_ros2) - For ROS2 users
-
-### AC Training Lab Resources
-- [AC Training Lab Documentation](https://ac-training-lab.readthedocs.io/)
-- [AC Training Lab GitHub](https://github.com/AccelerationConsortium/ac-training-lab)
-- [Discussions](https://github.com/AccelerationConsortium/ac-training-lab/discussions)
-
-## Contributing
-
-To contribute improvements or report issues:
-1. Open an issue on the [AC Training Lab GitHub](https://github.com/AccelerationConsortium/ac-training-lab/issues)
-2. Join the discussion at [AC Discussions](https://github.com/AccelerationConsortium/ac-training-lab/discussions)
-3. Submit a pull request with your changes
-
-## License
-
-This module is part of the ac-training-lab project and follows the same MIT License.
-
-## Support
-
-For support with:
-- **Hardware issues**: Contact UFACTORY support
-- **Software/integration issues**: Open an issue on GitHub
-- **General questions**: Join our discussions or reach out to sterling.baird@utoronto.ca
diff --git a/src/ac_training_lab/xarm5/__init__.py b/src/ac_training_lab/xarm5/__init__.py
deleted file mode 100644
index f380c29d..00000000
--- a/src/ac_training_lab/xarm5/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""XArm5 control module for Raspberry Pi 5.
-
-This module provides integration for controlling the UFACTORY xArm5 robotic arm
-from a Raspberry Pi 5 as part of the AC Training Lab's rail/lift/arm combo system.
-"""
-
-__version__ = "0.1.0"
diff --git a/src/ac_training_lab/xarm5/_scripts/basic_movement.py b/src/ac_training_lab/xarm5/_scripts/basic_movement.py
deleted file mode 100644
index 13920867..00000000
--- a/src/ac_training_lab/xarm5/_scripts/basic_movement.py
+++ /dev/null
@@ -1,110 +0,0 @@
-"""Basic movement example for xArm5.
-
-This script demonstrates basic movement commands for the xArm5 robotic arm.
-Includes connection, enabling motion, moving to positions, and safe shutdown.
-"""
-
-import time
-
-from xarm.wrapper import XArmAPI
-
-# Configuration
-XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
-
-
-def main():
- """Execute basic movement demonstration."""
- print("=" * 60)
- print("XArm5 Basic Movement Demo")
- print("=" * 60)
-
- # Connect to xArm5
- print(f"\nConnecting to xArm5 at {XARM_IP}...")
- arm = XArmAPI(XARM_IP)
-
- if not arm.connected:
- print("ā Failed to connect to xArm5")
- return
-
- print("ā Connected successfully")
- print(f" Robot version: {arm.version}")
- print(f" Robot state: {arm.state}")
-
- try:
- # Enable motion
- print("\nEnabling motion...")
- arm.motion_enable(enable=True)
- arm.set_mode(0) # Position control mode
- arm.set_state(0) # Ready state
- time.sleep(1)
-
- # Move to home position
- print("\nMoving to home position...")
- arm.move_gohome(wait=True)
- print("ā Reached home position")
- time.sleep(1)
-
- # Get current position
- position = arm.get_position()
- print("\nCurrent position:")
- print(f" X: {position[1][0]:.2f} mm")
- print(f" Y: {position[1][1]:.2f} mm")
- print(f" Z: {position[1][2]:.2f} mm")
- print(f" Roll: {position[1][3]:.2f}°")
- print(f" Pitch: {position[1][4]:.2f}°")
- print(f" Yaw: {position[1][5]:.2f}°")
-
- # Movement sequence
- print("\nExecuting movement sequence...")
-
- # Position 1
- print("\n Moving to position 1 (forward)...")
- arm.set_position(
- x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print(" ā Position 1 reached")
- time.sleep(1)
-
- # Position 2
- print("\n Moving to position 2 (left)...")
- arm.set_position(
- x=300, y=100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print(" ā Position 2 reached")
- time.sleep(1)
-
- # Position 3
- print("\n Moving to position 3 (right)...")
- arm.set_position(
- x=300, y=-100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print(" ā Position 3 reached")
- time.sleep(1)
-
- # Return to home
- print("\nReturning to home position...")
- arm.move_gohome(wait=True)
- print("ā Returned to home")
-
- print("\n" + "=" * 60)
- print("Movement sequence completed successfully!")
- print("=" * 60)
-
- except KeyboardInterrupt:
- print("\n\nā ļø Interrupted by user - stopping robot...")
- arm.emergency_stop()
-
- except Exception as e:
- print(f"\nā Error occurred: {e}")
- print("Attempting emergency stop...")
- arm.emergency_stop()
-
- finally:
- # Clean up
- print("\nDisconnecting...")
- arm.disconnect()
- print("ā Disconnected safely")
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/ac_training_lab/xarm5/_scripts/gripper_demo.py b/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
deleted file mode 100644
index 601cfc2e..00000000
--- a/src/ac_training_lab/xarm5/_scripts/gripper_demo.py
+++ /dev/null
@@ -1,205 +0,0 @@
-"""Gripper control demonstration for xArm5.
-
-This script demonstrates how to control various gripper types with the xArm5.
-Supports standard gripper, BIO gripper, and RobotIQ gripper.
-"""
-
-import time
-
-from xarm.wrapper import XArmAPI
-
-# Configuration
-XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
-GRIPPER_TYPE = "standard" # Options: "standard", "bio", "robotiq"
-
-
-def demo_standard_gripper(arm):
- """Demonstrate standard xArm gripper."""
- print("\n" + "-" * 60)
- print("Standard Gripper Demo")
- print("-" * 60)
-
- # Enable gripper
- print("\nEnabling gripper...")
- arm.set_gripper_enable(True)
- arm.set_gripper_mode(0)
- time.sleep(1)
-
- # Get current gripper position
- ret = arm.get_gripper_position()
- if ret[0] == 0:
- print(f"Current gripper position: {ret[1]} (0=closed, 800=open)")
-
- # Open gripper
- print("\nOpening gripper...")
- arm.set_gripper_position(800, wait=True)
- print("ā Gripper opened")
- time.sleep(1)
-
- # Partially close
- print("\nPartially closing gripper (400/800)...")
- arm.set_gripper_position(400, wait=True)
- print("ā Gripper at 50% position")
- time.sleep(1)
-
- # Close gripper
- print("\nClosing gripper...")
- arm.set_gripper_position(0, wait=True)
- print("ā Gripper closed")
- time.sleep(1)
-
- # Open again
- print("\nOpening gripper again...")
- arm.set_gripper_position(800, wait=True)
- print("ā Gripper opened")
-
-
-def demo_bio_gripper(arm):
- """Demonstrate BIO gripper."""
- print("\n" + "-" * 60)
- print("BIO Gripper Demo")
- print("-" * 60)
-
- # Enable BIO gripper
- print("\nEnabling BIO gripper...")
- arm.set_bio_gripper_enable(True)
- time.sleep(1)
-
- # Set speed
- arm.set_bio_gripper_speed(500)
-
- # Get status
- ret = arm.get_bio_gripper_status()
- if ret[0] == 0:
- print(f"BIO Gripper status: {ret[1]}")
-
- # Open gripper
- print("\nOpening BIO gripper...")
- arm.open_bio_gripper(wait=True)
- print("ā BIO gripper opened")
- time.sleep(1)
-
- # Close gripper
- print("\nClosing BIO gripper...")
- arm.close_bio_gripper(wait=True)
- print("ā BIO gripper closed")
- time.sleep(1)
-
- # Open again
- print("\nOpening BIO gripper again...")
- arm.open_bio_gripper(wait=True)
- print("ā BIO gripper opened")
-
-
-def demo_robotiq_gripper(arm):
- """Demonstrate RobotIQ gripper."""
- print("\n" + "-" * 60)
- print("RobotIQ Gripper Demo")
- print("-" * 60)
-
- # Reset and activate
- print("\nInitializing RobotIQ gripper...")
- arm.robotiq_reset()
- time.sleep(2)
- arm.robotiq_set_activate(True)
- time.sleep(2)
-
- # Get status
- ret = arm.robotiq_get_status()
- if ret[0] == 0:
- print(f"RobotIQ status: {ret[1]}")
-
- # Open gripper
- print("\nOpening RobotIQ gripper...")
- arm.robotiq_open(wait=True)
- print("ā RobotIQ gripper opened")
- time.sleep(1)
-
- # Set specific position
- print("\nSetting RobotIQ gripper to 50% (128/255)...")
- arm.robotiq_set_position(128, wait=True)
- print("ā RobotIQ gripper at 50% position")
- time.sleep(1)
-
- # Close gripper
- print("\nClosing RobotIQ gripper...")
- arm.robotiq_close(wait=True)
- print("ā RobotIQ gripper closed")
- time.sleep(1)
-
- # Open again
- print("\nOpening RobotIQ gripper again...")
- arm.robotiq_open(wait=True)
- print("ā RobotIQ gripper opened")
-
-
-def main():
- """Execute gripper demonstration."""
- print("=" * 60)
- print("XArm5 Gripper Control Demo")
- print(f"Gripper Type: {GRIPPER_TYPE.upper()}")
- print("=" * 60)
-
- # Connect to xArm5
- print(f"\nConnecting to xArm5 at {XARM_IP}...")
- arm = XArmAPI(XARM_IP)
-
- if not arm.connected:
- print("ā Failed to connect to xArm5")
- return
-
- print("ā Connected successfully")
-
- try:
- # Enable motion
- print("\nEnabling motion...")
- arm.motion_enable(enable=True)
- arm.set_mode(0)
- arm.set_state(0)
- time.sleep(1)
-
- # Move to working position
- print("\nMoving to working position...")
- arm.set_position(
- x=300, y=0, z=300, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print("ā In working position")
-
- # Execute gripper demo based on type
- if GRIPPER_TYPE.lower() == "standard":
- demo_standard_gripper(arm)
- elif GRIPPER_TYPE.lower() == "bio":
- demo_bio_gripper(arm)
- elif GRIPPER_TYPE.lower() == "robotiq":
- demo_robotiq_gripper(arm)
- else:
- print(f"\nā Unknown gripper type: {GRIPPER_TYPE}")
- print(" Valid options: standard, bio, robotiq")
-
- # Return to home
- print("\nReturning to home position...")
- arm.move_gohome(wait=True)
- print("ā Returned to home")
-
- print("\n" + "=" * 60)
- print("Gripper demo completed successfully!")
- print("=" * 60)
-
- except KeyboardInterrupt:
- print("\n\nā ļø Interrupted by user - stopping robot...")
- arm.emergency_stop()
-
- except Exception as e:
- print(f"\nā Error occurred: {e}")
- print("Attempting emergency stop...")
- arm.emergency_stop()
-
- finally:
- # Clean up
- print("\nDisconnecting...")
- arm.disconnect()
- print("ā Disconnected safely")
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/ac_training_lab/xarm5/_scripts/safety_demo.py b/src/ac_training_lab/xarm5/_scripts/safety_demo.py
deleted file mode 100644
index 89f8159e..00000000
--- a/src/ac_training_lab/xarm5/_scripts/safety_demo.py
+++ /dev/null
@@ -1,253 +0,0 @@
-"""Safety features demonstration for xArm5.
-
-This script demonstrates safety features including emergency stop,
-error handling, collision detection, and safe workspace limits.
-"""
-
-import time
-
-from xarm.wrapper import XArmAPI
-
-# Configuration
-XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
-
-# Workspace limits (in mm)
-WORKSPACE_LIMITS = {
- "x_min": 100,
- "x_max": 500,
- "y_min": -300,
- "y_max": 300,
- "z_min": 50,
- "z_max": 500,
-}
-
-
-def check_workspace_limits(position):
- """Check if position is within safe workspace limits.
-
- Args:
- position: Tuple of (x, y, z) coordinates in mm
-
- Returns:
- bool: True if position is safe, False otherwise
- """
- x, y, z = position
-
- if not (WORKSPACE_LIMITS["x_min"] <= x <= WORKSPACE_LIMITS["x_max"]):
- x_min = WORKSPACE_LIMITS["x_min"]
- x_max = WORKSPACE_LIMITS["x_max"]
- print(f"ā ļø X position {x} outside limits [{x_min}, {x_max}]")
- return False
-
- if not (WORKSPACE_LIMITS["y_min"] <= y <= WORKSPACE_LIMITS["y_max"]):
- y_min = WORKSPACE_LIMITS["y_min"]
- y_max = WORKSPACE_LIMITS["y_max"]
- print(f"ā ļø Y position {y} outside limits [{y_min}, {y_max}]")
- return False
-
- if not (WORKSPACE_LIMITS["z_min"] <= z <= WORKSPACE_LIMITS["z_max"]):
- z_min = WORKSPACE_LIMITS["z_min"]
- z_max = WORKSPACE_LIMITS["z_max"]
- print(f"ā ļø Z position {z} outside limits [{z_min}, {z_max}]")
- return False
-
- return True
-
-
-def demo_safe_movement(arm):
- """Demonstrate safe movement with workspace limits."""
- print("\n" + "-" * 60)
- print("Safe Movement Demo")
- print("-" * 60)
-
- # Test safe position
- safe_position = (300, 0, 200)
- print(f"\nTesting safe position {safe_position}...")
- if check_workspace_limits(safe_position):
- print("ā Position is safe - executing movement")
- arm.set_position(
- x=safe_position[0],
- y=safe_position[1],
- z=safe_position[2],
- roll=180,
- pitch=0,
- yaw=0,
- speed=100,
- wait=True,
- )
- print("ā Movement completed")
- time.sleep(1)
- else:
- print("ā Position rejected - outside workspace limits")
-
- # Test unsafe position (too far)
- unsafe_position = (700, 0, 200)
- print(f"\nTesting unsafe position {unsafe_position}...")
- if check_workspace_limits(unsafe_position):
- print("ā Position is safe - would execute movement")
- else:
- print("ā Position rejected - outside workspace limits")
- print(" Movement NOT executed for safety")
-
-
-def demo_error_handling(arm):
- """Demonstrate error detection and recovery."""
- print("\n" + "-" * 60)
- print("Error Handling Demo")
- print("-" * 60)
-
- # Check for errors
- err_code = arm.error_code
- warn_code = arm.warn_code
-
- print(f"\nCurrent error code: {err_code}")
- print(f"Current warning code: {warn_code}")
-
- if arm.has_error:
- print("ā ļø Robot has errors - attempting to clear...")
- arm.clean_error()
- time.sleep(1)
-
- if arm.has_error:
- print("ā Failed to clear errors")
- else:
- print("ā Errors cleared successfully")
- else:
- print("ā No errors detected")
-
- if arm.has_warn:
- print("ā ļø Robot has warnings - attempting to clear...")
- arm.clean_warn()
- time.sleep(1)
-
- if arm.has_warn:
- print("ā Failed to clear warnings")
- else:
- print("ā Warnings cleared successfully")
- else:
- print("ā No warnings detected")
-
-
-def demo_collision_sensitivity(arm):
- """Demonstrate collision sensitivity settings."""
- print("\n" + "-" * 60)
- print("Collision Sensitivity Demo")
- print("-" * 60)
-
- # Set collision sensitivity (0-5, where 0 is least sensitive)
- sensitivity = 3
- print(f"\nSetting collision sensitivity to {sensitivity}...")
- arm.set_collision_sensitivity(sensitivity)
- time.sleep(0.5)
-
- print(f"ā Collision sensitivity set to {arm.collision_sensitivity}")
- print(" Note: Higher values = more sensitive collision detection")
-
-
-def demo_emergency_stop(arm):
- """Demonstrate emergency stop functionality."""
- print("\n" + "-" * 60)
- print("Emergency Stop Demo")
- print("-" * 60)
-
- print("\nStarting a slow movement...")
- print("(In a real emergency, you would call arm.emergency_stop())")
-
- # Start a movement
- arm.set_position(
- x=350,
- y=0,
- z=200,
- roll=180,
- pitch=0,
- yaw=0,
- speed=50, # Slow speed for demonstration
- wait=False, # Don't wait for completion
- )
-
- # Simulate waiting a bit
- time.sleep(0.5)
-
- # Emergency stop
- print("\nā ļø EMERGENCY STOP triggered!")
- arm.emergency_stop()
- print("ā Robot stopped immediately")
-
- # Recovery process
- print("\nRecovering from emergency stop...")
- time.sleep(1)
-
- # Clear errors and reset state
- arm.clean_error()
- arm.set_state(0)
- time.sleep(1)
-
- print("ā Robot recovered and ready")
-
-
-def main():
- """Execute safety features demonstration."""
- print("=" * 60)
- print("XArm5 Safety Features Demo")
- print("=" * 60)
-
- print("\nWorkspace Limits:")
- for key, value in WORKSPACE_LIMITS.items():
- print(f" {key}: {value} mm")
-
- # Connect to xArm5
- print(f"\nConnecting to xArm5 at {XARM_IP}...")
- arm = XArmAPI(XARM_IP)
-
- if not arm.connected:
- print("ā Failed to connect to xArm5")
- return
-
- print("ā Connected successfully")
-
- try:
- # Enable motion
- print("\nEnabling motion...")
- arm.motion_enable(enable=True)
- arm.set_mode(0)
- arm.set_state(0)
- time.sleep(1)
-
- # Move to starting position
- print("\nMoving to starting position...")
- arm.move_gohome(wait=True)
- print("ā At starting position")
-
- # Run safety demonstrations
- demo_error_handling(arm)
- demo_collision_sensitivity(arm)
- demo_safe_movement(arm)
- demo_emergency_stop(arm)
-
- # Return to home
- print("\nReturning to home position...")
- arm.move_gohome(wait=True)
- print("ā Returned to home")
-
- print("\n" + "=" * 60)
- print("Safety demo completed successfully!")
- print("=" * 60)
-
- except KeyboardInterrupt:
- print("\n\nā ļø Interrupted by user - stopping robot...")
- arm.emergency_stop()
-
- except Exception as e:
- print(f"\nā Error occurred: {e}")
- print("Attempting emergency stop...")
- arm.emergency_stop()
-
- finally:
- # Clean up
- print("\nDisconnecting...")
- arm.disconnect()
- print("ā Disconnected safely")
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/ac_training_lab/xarm5/_scripts/track_integration.py b/src/ac_training_lab/xarm5/_scripts/track_integration.py
deleted file mode 100644
index d7cbba37..00000000
--- a/src/ac_training_lab/xarm5/_scripts/track_integration.py
+++ /dev/null
@@ -1,183 +0,0 @@
-"""Linear track integration demonstration for xArm5.
-
-This script demonstrates how to control the xArm5 with a linear track (rail)
-for extended workspace and coordinated movements.
-"""
-
-import time
-
-from xarm.wrapper import XArmAPI
-
-# Configuration
-XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
-TRACK_ENABLED = True # Set to False if no track is attached
-
-
-def main():
- """Execute linear track integration demonstration."""
- print("=" * 60)
- print("XArm5 Linear Track Integration Demo")
- print("=" * 60)
-
- # Connect to xArm5 with track enabled
- print(f"\nConnecting to xArm5 at {XARM_IP}...")
- if TRACK_ENABLED:
- print("Track mode: ENABLED")
- arm = XArmAPI(XARM_IP, enable_track=True)
- else:
- print("Track mode: DISABLED")
- arm = XArmAPI(XARM_IP)
-
- if not arm.connected:
- print("ā Failed to connect to xArm5")
- return
-
- print("ā Connected successfully")
- print(f" Robot version: {arm.version}")
-
- try:
- # Enable motion
- print("\nEnabling motion...")
- arm.motion_enable(enable=True)
- arm.set_mode(0)
- arm.set_state(0)
- time.sleep(1)
-
- if TRACK_ENABLED:
- # Linear track operations
- print("\n" + "-" * 60)
- print("Linear Track Control")
- print("-" * 60)
-
- # Enable linear motor
- print("\nEnabling linear motor (track)...")
- arm.set_linear_motor_enable(True)
- time.sleep(1)
-
- # Get track status
- ret = arm.get_linear_motor_status()
- print(f"Track status: {ret}")
-
- # Get current track position
- ret = arm.get_linear_motor_pos()
- if ret[0] == 0:
- current_pos = ret[1]
- print(f"Current track position: {current_pos} mm")
-
- # Move track to position 1 (0 mm - home)
- print("\nMoving track to home position (0 mm)...")
- arm.set_linear_motor_pos(0, wait=True)
- print("ā Track at home position")
- time.sleep(1)
-
- # Verify position
- ret = arm.get_linear_motor_pos()
- if ret[0] == 0:
- print(f" Confirmed position: {ret[1]} mm")
-
- # Move track to position 2 (300 mm)
- print("\nMoving track to position 300 mm...")
- arm.set_linear_motor_pos(300, wait=True)
- print("ā Track at 300 mm")
- time.sleep(1)
-
- # Verify position
- ret = arm.get_linear_motor_pos()
- if ret[0] == 0:
- print(f" Confirmed position: {ret[1]} mm")
-
- # Move track to position 3 (600 mm)
- print("\nMoving track to position 600 mm...")
- arm.set_linear_motor_pos(600, wait=True)
- print("ā Track at 600 mm")
- time.sleep(1)
-
- # Verify position
- ret = arm.get_linear_motor_pos()
- if ret[0] == 0:
- print(f" Confirmed position: {ret[1]} mm")
-
- # Coordinated arm and track movement
- print("\n" + "-" * 60)
- print("Coordinated Arm + Track Movement")
- print("-" * 60)
-
- # Position 1: Track at 0, arm reaching forward
- print("\nPosition 1: Track home, arm forward...")
- arm.set_linear_motor_pos(0, wait=True)
- arm.set_position(
- x=400, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print("ā Position 1 reached")
- time.sleep(1)
-
- # Position 2: Track at 300, arm centered
- print("\nPosition 2: Track 300mm, arm centered...")
- arm.set_linear_motor_pos(300, wait=True)
- arm.set_position(
- x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print("ā Position 2 reached")
- time.sleep(1)
-
- # Position 3: Track at 600, arm reaching back
- print("\nPosition 3: Track 600mm, arm back...")
- arm.set_linear_motor_pos(600, wait=True)
- arm.set_position(
- x=200, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- print("ā Position 3 reached")
- time.sleep(1)
-
- # Return track to home
- print("\nReturning track to home position...")
- arm.set_linear_motor_pos(0, wait=True)
- print("ā Track at home")
-
- else:
- # Just arm movements without track
- print("\n(Track disabled - performing arm-only movements)")
-
- print("\nMoving to position 1...")
- arm.set_position(
- x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- time.sleep(1)
-
- print("\nMoving to position 2...")
- arm.set_position(
- x=300, y=100, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True
- )
- time.sleep(1)
-
- # Return arm to home
- print("\nReturning arm to home position...")
- arm.move_gohome(wait=True)
- print("ā Arm at home")
-
- print("\n" + "=" * 60)
- print("Track integration demo completed successfully!")
- print("=" * 60)
-
- except KeyboardInterrupt:
- print("\n\nā ļø Interrupted by user - stopping robot...")
- arm.emergency_stop()
- if TRACK_ENABLED:
- arm.set_linear_motor_stop()
-
- except Exception as e:
- print(f"\nā Error occurred: {e}")
- print("Attempting emergency stop...")
- arm.emergency_stop()
- if TRACK_ENABLED:
- arm.set_linear_motor_stop()
-
- finally:
- # Clean up
- print("\nDisconnecting...")
- arm.disconnect()
- print("ā Disconnected safely")
-
-
-if __name__ == "__main__":
- main()
diff --git a/src/ac_training_lab/xarm5/requirements.txt b/src/ac_training_lab/xarm5/requirements.txt
deleted file mode 100644
index c6c9a58f..00000000
--- a/src/ac_training_lab/xarm5/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-# XArm5 Raspberry Pi Control Requirements
-
-
-# Core dependencies (usually included with SDK)
-numpy>=1.20.0
-# Official UFACTORY xArm Python SDK
-xarm-python-sdk>=1.17.0
diff --git a/src/ac_training_lab/xarm5_minimal_example.py b/src/ac_training_lab/xarm5_minimal_example.py
new file mode 100644
index 00000000..f0d03e9a
--- /dev/null
+++ b/src/ac_training_lab/xarm5_minimal_example.py
@@ -0,0 +1,39 @@
+"""Minimal example for controlling UFACTORY xArm5 from Raspberry Pi 5.
+
+This script demonstrates basic programmatic control of the xArm5 robotic arm.
+
+Installation:
+ pip install xarm-python-sdk
+
+Usage:
+ python xarm5_minimal_example.py
+"""
+
+from xarm.wrapper import XArmAPI
+
+# Configuration
+XARM_IP = "192.168.1.100" # Replace with your xArm5 IP address
+
+
+def main():
+ """Minimal example of xArm5 control."""
+ # Connect to xArm5
+ arm = XArmAPI(XARM_IP)
+
+ # Enable motion
+ arm.motion_enable(enable=True)
+ arm.set_mode(0) # Position control mode
+ arm.set_state(0) # Ready state
+
+ # Move to home position
+ arm.move_gohome(wait=True)
+
+ # Move to a position (x, y, z in mm, angles in degrees)
+ arm.set_position(x=300, y=0, z=200, roll=180, pitch=0, yaw=0, speed=100, wait=True)
+
+ # Disconnect
+ arm.disconnect()
+
+
+if __name__ == "__main__":
+ main()