$npx -y skills add arpitg1304/robotics-agent-skills --skill robotics-software-principlesFoundational software design principles applied specifically to robotics module development. Use this skill when designing robot software modules, structuring codebases, making architecture decisions, reviewing robotics code, or building reusable robotics libraries. Trigger whene
| 1 | # Robotics Software Design Principles |
| 2 | |
| 3 | ## Why Robotics Software Is Different |
| 4 | |
| 5 | Robotics code operates under constraints that most software never faces: |
| 6 | |
| 7 | 1. **Physical consequences** — A bug doesn't just crash a process, it crashes a robot into a wall |
| 8 | 2. **Real-time deadlines** — Missing a 1ms control loop deadline can cause oscillation or damage |
| 9 | 3. **Sensor uncertainty** — All inputs are noisy, delayed, and occasionally wrong |
| 10 | 4. **Hardware diversity** — Same algorithm must work on 10 different grippers from 5 vendors |
| 11 | 5. **Sim-to-real gap** — Code must run identically in simulation and on real hardware |
| 12 | 6. **Long-running operation** — Robots run for hours/days; memory leaks and drift matter |
| 13 | 7. **Safety criticality** — Some failures must NEVER happen, regardless of software state |
| 14 | |
| 15 | These constraints demand disciplined design. Below are principles that account for them. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Principle 1: Single Responsibility — One Module, One Job |
| 20 | |
| 21 | Every module (node, class, function) should have exactly ONE reason to change. |
| 22 | |
| 23 | **Why it matters in robotics**: A perception module that also does control means a camera driver update can break your arm controller. In safety-critical systems, this coupling is unacceptable. |
| 24 | |
| 25 | ```python |
| 26 | # ❌ BAD: God module — perception + planning + control + logging |
| 27 | class RobotController: |
| 28 | def __init__(self): |
| 29 | self.camera = RealSenseCamera() |
| 30 | self.detector = YOLODetector() |
| 31 | self.planner = RRTPlanner() |
| 32 | self.arm = UR5Driver() |
| 33 | self.logger = DataLogger() |
| 34 | |
| 35 | def run(self): |
| 36 | image = self.camera.capture() |
| 37 | objects = self.detector.detect(image) |
| 38 | path = self.planner.plan(objects[0].pose) |
| 39 | self.arm.execute(path) |
| 40 | self.logger.log(image, objects, path) |
| 41 | # If ANY of these changes, you touch this class |
| 42 | |
| 43 | # ✅ GOOD: Separated responsibilities with clear interfaces |
| 44 | class PerceptionModule: |
| 45 | """ONLY responsibility: raw sensor data → detected objects""" |
| 46 | def __init__(self, camera: CameraInterface, detector: DetectorInterface): |
| 47 | self.camera = camera |
| 48 | self.detector = detector |
| 49 | |
| 50 | def get_detections(self) -> List[Detection]: |
| 51 | image = self.camera.capture() |
| 52 | return self.detector.detect(image) |
| 53 | |
| 54 | class PlanningModule: |
| 55 | """ONLY responsibility: goal + world state → trajectory""" |
| 56 | def __init__(self, planner: PlannerInterface): |
| 57 | self.planner = planner |
| 58 | |
| 59 | def plan_to(self, target: Pose, obstacles: List[Obstacle]) -> Trajectory: |
| 60 | return self.planner.plan(target, obstacles) |
| 61 | |
| 62 | class ExecutionModule: |
| 63 | """ONLY responsibility: trajectory → hardware commands""" |
| 64 | def __init__(self, arm: ArmInterface): |
| 65 | self.arm = arm |
| 66 | |
| 67 | def execute(self, trajectory: Trajectory) -> ExecutionResult: |
| 68 | return self.arm.follow_trajectory(trajectory) |
| 69 | ``` |
| 70 | |
| 71 | **Test**: Can you describe what a module does WITHOUT using "and"? If not, split it. |
| 72 | |
| 73 | --- |
| 74 | |
| 75 | ## Principle 2: Dependency Inversion — Depend on Abstractions, Not Hardware |
| 76 | |
| 77 | High-level modules (planning, behavior) should never depend on low-level modules (drivers, hardware). Both should depend on abstractions. |
| 78 | |
| 79 | **Why it matters in robotics**: This is the foundation of sim-to-real. If your planner imports `UR5Driver` directly, it can't run in simulation. If it depends on `ArmInterface`, you swap implementations freely. |
| 80 | |
| 81 | ```python |
| 82 | from abc import ABC, abstractmethod |
| 83 | from dataclasses import dataclass |
| 84 | from typing import List, Optional |
| 85 | import numpy as np |
| 86 | |
| 87 | # ─── ABSTRACTIONS (the contracts) ──────────────────────────── |
| 88 | |
| 89 | class ArmInterface(ABC): |
| 90 | """Abstract arm — every arm implementation must honor this contract""" |
| 91 | |
| 92 | @abstractmethod |
| 93 | def get_joint_positions(self) -> np.ndarray: |
| 94 | """Returns current joint positions in radians""" |
| 95 | ... |
| 96 | |
| 97 | @abstractmethod |
| 98 | def get_ee_pose(self) -> Pose: |
| 99 | """Returns current end-effector pose""" |
| 100 | ... |
| 101 | |
| 102 | @abstractmethod |
| 103 | def move_to_joints(self, positions: np.ndarray, |
| 104 | velocity: float = 0.5) -> bool: |
| 105 | """Move to joint positions. Returns True on success.""" |
| 106 | ... |
| 107 | |
| 108 | @abstractme |