Skip to content

fix: make RoboticArm CSV import/export work for arms with fewer than 4 servos - #289

Open
thisisanubhav wants to merge 5 commits into
fossasia:mainfrom
thisisanubhav:fix/robotic-arm-csv-servo-count
Open

thisisanubhav wants to merge 5 commits into
fossasia:mainfrom
thisisanubhav:fix/robotic-arm-csv-servo-count

Conversation

@thisisanubhav

@thisisanubhav thisisanubhav commented Sep 22, 2026 •

Copy link
Copy Markdown

Fixes #288

Changes

  • export_timeline_to_csv pads each row with null up to four servos, so rows always match the Servo1–Servo4 header. The file format is unchanged.
  • import_timeline_from_csv returns one angle per servo on this arm, which is what run_schedule expects. It raises ValueError if the file sets an angle for a servo the arm doesn't have, and treats values missing from short rows (written by the old export) as unset.
  • New tests/test_robotic_arm.py, which needs no hardware: round trips for 1–4 servo arms, running an imported timeline, short rows, and the rejection case.

Testing

  • pytest tests/test_robotic_arm.py: 8 passed. With the previous motor.py, 7 of them fail.
  • black --check, flake8, pydocstyle and bandit are clean on pslab/external/motor.py, which is in the lint set.

No UI changes.

Summary by Sourcery

Fix RoboticArm CSV timeline handling for arms with fewer than four servos.

Bug Fixes:

  • Make RoboticArm timeline CSV import and export support arms with one to four servos while preserving the four-servo file format.
  • Reject CSV timelines that specify angles for servos unavailable on the arm and reject malformed servo columns or oversized export rows.

Tests:

  • Add hardware-independent coverage for CSV round trips, padded and short rows, schedule execution, and invalid servo data.

Summary by CodeRabbit

  • Bug Fixes
    • Improved robotic arm timeline CSV import to handle missing, empty, and null servo values.
    • Added validation for missing servo columns and rows containing more angles than the connected arm supports.
    • Improved compatibility with arms using fewer than the maximum number of servos.
    • Ensured exported timeline rows consistently include all servo columns, using null for unavailable values.
    • Added validation to prevent exporting rows with more angles than supported.

The CSV header always lists Servo1-Servo4, but each row only held one
value per servo on the arm, so a timeline exported from a 2-servo arm
had short rows that import_timeline_from_csv could not read.
import_timeline_from_csv always returned four angles per timestep, but
run_schedule requires exactly one per servo, so a 1-3 servo arm could
not run a timeline it imported. Return angles for this arm's servos
only, reject files that set an angle for a servo the arm lacks, and
treat values missing from short rows as unset.
Exercise export/import for 1-4 servo arms, running an imported
timeline, short rows, and rejecting angles for servos the arm lacks.
@sourcery-ai

sourcery-ai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Updates RoboticArm CSV serialization for variable servo counts: exports retain the four-servo format, while imports handle legacy short rows, return timelines sized for the current arm, and reject angles for unavailable servos; hardware-free tests cover the supported cases.

Sequence diagram for variable-servo timeline CSV round trip

sequenceDiagram
    participant Caller
    participant RoboticArm
    participant CSV

    Caller->>RoboticArm: export_timeline_to_csv(timeline, filepath)
    RoboticArm->>CSV: write Servo1-Servo4 header
    RoboticArm->>CSV: write padded rows with null values
    Caller->>RoboticArm: import_timeline_from_csv(filepath)
    RoboticArm->>CSV: read CSV rows
    RoboticArm->>RoboticArm: return angles[:len(servos)]
    RoboticArm-->>Caller: timeline sized for current arm

    alt unavailable servo has an angle
        RoboticArm-->>Caller: raise ValueError
    else short row or trailing nulls
        RoboticArm-->>Caller: treat missing values as unset
    end
Loading

File-Level Changes

Change Details Files
Make CSV export produce a fixed four-servo shape while making import return only the angles supported by the current arm.
  • Pad exported rows with null values through Servo4 without changing the header or file format.
  • Treat missing, empty, and null fields as unset, including trailing fields absent from legacy short rows.
  • Trim imported timelines to the arm’s servo count so they can be passed directly to run_schedule.
  • Reject non-null angles targeting servos unavailable on the arm.
pslab/external/motor.py
Add hardware-independent coverage for servo-count-specific CSV behavior and schedule execution.
  • Test round trips for arms with one through four servos.
  • Verify row padding, short-row compatibility, imported schedule execution, and rejection of extra servo angles.
tests/test_robotic_arm.py

Assessment against linked issues

Issue Objective Addressed Explanation
#288 Ensure CSV export produces rows compatible with the existing four-servo header, including padding rows with null values for arms with fewer than four servos. ✅
#288 Ensure CSV import returns exactly one angle per servo on the current arm, supports short legacy rows, and rejects angles specified for servos the arm does not have. ✅
#288 Allow timelines exported from arms with one to three servos to be imported and run successfully on the same arm. ✅

Possibly linked issues


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

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e0f1ecea-6ec1-4d5a-b13c-d9f7e3adbbd4

📥 Commits

Reviewing files that changed from the base of the PR and between 341dba3 and 7e25a47.

📒 Files selected for processing (2)
  • pslab/external/motor.py
  • tests/test_robotic_arm.py

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

RoboticArm CSV import and export now support arms with one to four servos. Export pads rows to four columns. Import validates columns, handles unset values, returns only configured servo angles, and rejects extra angles. Tests cover round trips and validation.

Changes

CSV timeline compatibility

Layer / File(s) Summary
CSV import handling
pslab/external/motor.py, tests/test_robotic_arm.py
Import validates the Servo1–Servo4 columns, handles None, empty strings, and "null" as unset values, and rejects angles beyond the arm's servo count.
CSV export handling
pslab/external/motor.py, tests/test_robotic_arm.py
Export rejects rows with more than four angles and pads shorter rows to four servo columns with null.
CSV compatibility tests
tests/test_robotic_arm.py
Tests cover mock-backed arms, round trips for one to four servos, padding, short rows, schedule execution, and validation errors.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 7e25a

Timelines for one- to four-servo arms preserve the existing CSV format, reject malformed data, and remain executable, so no merge-blocking risk is evident.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: fixing RoboticArm CSV import and export for arms with fewer than four servos.
Linked Issues check ✅ Passed The changes satisfy the coding requirements in #288. Export pads each row with null through Servo4 and rejects rows with more than four angles. Import requires the four servo columns, treats missi…
Out of Scope Changes check ✅ Passed The changes stay within #288. They update RoboticArm CSV import and export and add hardware-independent tests for the required behavior. No unrelated changes are identified.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

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

## Individual Comments

### Comment 1
<location path="pslab/external/motor.py" line_range="134-135" />
<code_context>
-                    value = row[key]
-                    if value == "null":
+                for i in range(1, RoboticArm.MAX_SERVOS + 1):
+                    value = row.get(f"Servo{i}")
+                    # Short rows from older exports leave trailing servos unset.
+                    if value in (None, "", "null"):
                         angles.append(None)
                     else:
</code_context>
<issue_to_address>
**issue (bug_risk):** A CSV with a missing servo column is treated as a valid short row because `row.get(...)` returns `None` for both an absent header and an absent cell. The importer silently returns unset angles and can discard values supplied in extra fields, whereas the previous implementation rejected a missing header with `KeyError`.

**Triggers:** When the input CSV header omits one or more of the Servo1–Servo4 columns.

**Suggested fix:** Validate that all four servo columns are present in `reader.fieldnames`; only treat missing values in an otherwise valid row as unset.

```suggestion
            reader = csv.DictReader(csvfile)
            if reader.fieldnames is None or any(
                f"Servo{i}" not in reader.fieldnames
                for i in range(1, RoboticArm.MAX_SERVOS + 1)
            ):
                raise ValueError("CSV must contain all four servo columns")
            for row in reader:
```
</issue_to_address>

### Comment 2
<location path="pslab/external/motor.py" line_range="175" />
<code_context>
             writer.writerow(["Timestep", "Servo1", "Servo2", "Servo3", "Servo4"])
             for i, row in enumerate(timeline):
+                # Pad to four servos so every row matches the header.
+                row = list(row) + [None] * (RoboticArm.MAX_SERVOS - len(row))
                 pos = ["null" if val is None else val for val in row]
                 writer.writerow([i] + pos)
</code_context>
<issue_to_address>
**nitpick (bug_risk):** Rows containing more than four servo values are not truncated or rejected: the negative padding count adds nothing, and `writer.writerow` emits extra fields beyond the four servo columns in the header. Thus the exporter does not guarantee that every row matches the declared format.

**Triggers:** When a caller passes a timeline row with more than `RoboticArm.MAX_SERVOS` values.

**Suggested fix:** Reject rows longer than `RoboticArm.MAX_SERVOS` before writing, or explicitly truncate them if that is the intended contract.

```suggestion
                row = list(row)[: RoboticArm.MAX_SERVOS] + [None] * (RoboticArm.MAX_SERVOS - len(row))
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: pslab/external/motor.py:135


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread pslab/external/motor.py
Comment thread pslab/external/motor.py
Importing now requires the Servo1-Servo4 header columns instead of
treating a missing column like an unset angle, and exporting rejects
timesteps with more than four angles before creating the file.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sourcery assessment

Approved.

This branch has not been deployed

No deployments
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 CSV import/export fails for arms with fewer than 4 servos

1 participant