Skip to content

feat: Added RoboticArm #255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Conversation

rahul31124
Copy link

@rahul31124 rahul31124 commented Jul 10, 2025

Fixes #252 #253

Changes

Added RoboticArm class to pslab.external.motor
This class enables controlling RoboticArm

Supports control of up to 4 servos via Servo class
Internally uses existing Servo objects, validating servo count and managing individual angle settings

Implemented run_schedule() to move RoboticArm according to a timeline
Each servo position is updated per second based on a time-dependent list of angle

Summary by Sourcery

Add RoboticArm class to motor module enabling multi-servo control and timed movement scheduling

New Features:

  • Add RoboticArm class with validation for up to 4 Servo instances
  • Implement run_schedule method to execute servo angle timelines at one-second intervals

Copy link

sourcery-ai bot commented Jul 10, 2025

Reviewer's Guide

Adds a new RoboticArm class in pslab.external.motor to coordinate up to four existing Servo instances, including input validation and a run_schedule method for time-based servo angle sequences.

Class diagram for the new RoboticArm class and its relationship to Servo

classDiagram
    class Servo {
        angle
        _get_duty_cycle(angle)
    }
    class RoboticArm {
        MAX_SERVOS
        servos
        __init__(servos)
        run_schedule(timeline)
    }
    RoboticArm --> "*" Servo : uses up to 4
Loading

File-Level Changes

Change Details Files
Introduce RoboticArm class with servo count validation
  • Define MAX_SERVOS constant
  • Validate number of servos in init and raise ValueError if exceeded
  • Store provided Servo instances in self.servos
pslab/external/motor.py
Implement run_schedule for timed servo movements
  • Loop over each time step in the timeline
  • Assign each servo’s angle from the corresponding timeline entry
  • Sleep for 1 second between steps
pslab/external/motor.py

Assessment against linked issues

Issue Objective Addressed Explanation
#252 Create a RoboticArm class that takes a list of 1-4 Servos as input.
#252 The RoboticArm class should be able to set the position of each servo independently.

Possibly linked issues

  • #0: PR adds RoboticArm class and run_schedule method, implementing issue requirements.
  • RoboticArm initial skeleton #252: The PR adds the RoboticArm class and the run_schedule method to implement the required time-dependent position schedule.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@rahul31124 rahul31124 requested a review from bessman July 10, 2025 15:20
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rahul31124 - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `pslab/external/motor.py:77` </location>
<code_context>
         return angle / (self._frequency**-1 * MICROSECONDS)
+
+
+class RoboticArm:
+    MAX_SERVOS = 4
+
</code_context>

<issue_to_address>
Consider validating timeline dimensions in run_schedule.

Currently, run_schedule may raise an IndexError or ignore extra values if a sublist's length doesn't match self.servos. Adding a check for sublist length would improve robustness.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
class RoboticArm:
    MAX_SERVOS = 4

=======
class RoboticArm:
    MAX_SERVOS = 4

    def run_schedule(self, timeline: List[List[Union[int, float]]]):
        """
        Run a schedule of servo positions.

        Args:
            timeline: A list of lists, where each sublist contains positions for each servo at a time step.

        Raises:
            ValueError: If any sublist does not match the number of servos.
        """
        for idx, positions in enumerate(timeline):
            if len(positions) != len(self.servos):
                raise ValueError(
                    f"Timeline entry at index {idx} has {len(positions)} positions, "
                    f"but {len(self.servos)} servos are configured."
                )
        # Existing logic for running the schedule goes here
        # for positions in timeline:
        #     self.set_positions(positions)
        #     time.sleep(self.step_delay)
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `pslab/external/motor.py:92` </location>
<code_context>
+            for j in range(len(self.servos)):
+                angle = timeline[i][j]
+                self.servos[j].angle = angle
+            time.sleep(1)
</code_context>

<issue_to_address>
Consider making the sleep duration configurable.

Allow users to set the sleep interval via a parameter or class attribute for greater flexibility.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 77 to 79
class RoboticArm:
MAX_SERVOS = 4

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider validating timeline dimensions in run_schedule.

Currently, run_schedule may raise an IndexError or ignore extra values if a sublist's length doesn't match self.servos. Adding a check for sublist length would improve robustness.

Suggested change
class RoboticArm:
MAX_SERVOS = 4
class RoboticArm:
MAX_SERVOS = 4
def run_schedule(self, timeline: List[List[Union[int, float]]]):
"""
Run a schedule of servo positions.
Args:
timeline: A list of lists, where each sublist contains positions for each servo at a time step.
Raises:
ValueError: If any sublist does not match the number of servos.
"""
for idx, positions in enumerate(timeline):
if len(positions) != len(self.servos):
raise ValueError(
f"Timeline entry at index {idx} has {len(positions)} positions, "
f"but {len(self.servos)} servos are configured."
)
# Existing logic for running the schedule goes here
# for positions in timeline:
# self.set_positions(positions)
# time.sleep(self.step_delay)

for j in range(len(self.servos)):
angle = timeline[i][j]
self.servos[j].angle = angle
time.sleep(1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider making the sleep duration configurable.

Allow users to set the sleep interval via a parameter or class attribute for greater flexibility.

Comment on lines 88 to 92
for i in range(len(timeline)):
for j in range(len(self.servos)):
angle = timeline[i][j]
self.servos[j].angle = angle
time.sleep(1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Replace index in for loop with direct reference (for-index-replacement)

@rahul31124
Copy link
Author

Hello @bessman, could you please review this PR and suggest any changes that might be needed?

Comment on lines 91 to 94
for i in range(len(timeline)):
for j in range(len(self.servos)):
angle = timeline[i][j]
self.servos[j].angle = angle
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use for-each style loops instead of range(len(..)):

Suggested change
for i in range(len(timeline)):
for j in range(len(self.servos)):
angle = timeline[i][j]
self.servos[j].angle = angle
for tl in timeline:
for i, s in enumerate(self.servos):
angle = tl[i]
s.angle = angle

Also add a check that all timelines are the same length:

tl_len = len(timeline[0])
if not all(len(tl) == tl_len for tl in timeline):
    raise ValueError

for j in range(len(self.servos)):
angle = timeline[i][j]
self.servos[j].angle = angle
time.sleep(1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is one second fine-grained enough? Seems like this should be smaller or configurable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, on the mobile app, is set to 1 second. However, I agree that making it configurable would offer more flexibility. I will update the method to include a parameter, defaulting to 1 second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RoboticArm initial skeleton
2 participants