All work

My first job out of grad school was building robots that clean hotel bathrooms.

July 2022 to April 2024 at Peanut Robotics as a Robotics Engineer, owning the manipulation subsystem, sensor integration, perception, and operator tooling on a mobile manipulator deployed in real hotels.

Role Robotics Engineer
Timeline Jul 2022 to Apr 2024
Stack C++ · Python · ROS · TOPPRA · OpenCV · Open3D
Sensors Dynamixel · Orbbec · Optitrack · Hall · Crush
What came out of it:

I joined Peanut right out of CMU. The office was a single room in San Francisco - around 19 people, and a lot of robots. Peanut built custom mobile manipulators tasked for cleaning bathrooms in commercial hotels. We were deployed in 2 hotels, with 10 active robots cleaning and getting paid by the hour.

As a Robotics Engineer, I worked cross-functionally across the manipulation stack, navigation, hardware, testing and sensor integration. My main ownership was the robot arm and manipulation stack. My primary projects focused on manipulation.

StartupHQ building directory, 880 Harrison Street, Peanut Robotics listed
880 Harrison St, Peanut's first office.
Row of Peanut Robotics mobile manipulators lined up in the lab
The robot fleet at the Folsom office.

1. Live Trajectory

The problem

Operators taught trajectories with a joystick, jogging the arm to capture joint positions one at a time and chaining them into a path. For a complex object like a toilet, that took a skilled operator 2 to 4 hours, plus a lot of retraining. Since every new location had to be taught this way, teaching speed was a hard ceiling on how fast Peanut could scale.

What I did

Built a live trajectory system that let operators teleoperate the arm by hand, physically guiding it through the cleaning motion while it recorded the trajectory directly. The work wasn't just capture: I had to plug into the existing system's recovery behaviors for failure cases, spraying logic, and more. Under the hood the captured path is time-parameterized with TOPPRA and executed through ros_control with the tool actuating in sync. Getting it right meant going deep on the operators' actual pain points and building something they would genuinely use.

Path planning diagram, operator keyframes and robot motion
Path planning: the operator positions keyframes, and the robot interpolates and executes.
Hand-drawn architecture diagram of the arm trajectory generation pipeline
Arm trajectory generation architecture, from move request to TOPPRA execution.
Live trajectory recording, guiding the wrist by hand.
Live trajectory: scrubber actuation (sped up).
Result: the robot scrubbing a surface using a live-recorded trajectory.

Impact

The training-time number is the headline, but it was not the only thing that changed. Four effects came out of it:

That third point is the one with the longest tail. Cheap trajectories turned into a testing instrument for the rest of the robot, and the team used it to run:

Where it fell short

Where it was going next

2. Room to Room Variation

The problem

Even hotel bathrooms with the same layout vary, since fixtures sit a little differently from room to room. A trajectory taught in one bathroom doesn't directly transfer to the next: run it as-is and the arm collides or misses the surface, and the system can't scale to many rooms. I ran an in-depth study of where the variation actually lived, and found the toilet pose varies the most.

What I did

Built a point cloud registration pipeline that measures how far the toilet has shifted in a given bathroom, then applies that delta to the cleaning trajectory, either by moving the base or by shifting the arm trajectory itself. Used passive stereo depth (ZEDx) for the scan, and experimented with Cartesian planning through mission control to apply the correction.

Outcome: one taught trajectory generalizes across rooms. Instead of re-teaching every bathroom, the robot measures the shift and adapts, turning the operator-taught approach into something that can actually scale across a hotel.
Top-down bathroom maps showing toilet pose variation between rooms
The toilet pose varies the most between bathrooms, the result of the variance study.
RANSAC + ICP point cloud registration results with error table
Registration results: aligning the captured cloud to the reference to measure the shift, with error against ground truth.

Getting reliable depth in a bathroom

The problem

Registration is only as good as the depth behind it, and bathrooms are a worst case for depth cameras. Light bounces off glossy plastic seats and porcelain (specular surfaces) and you get wavy, unreliable point clouds. On top of that, the robot needed four cameras running at once without one silently dropping out and degrading the whole stack.

What I did

Evaluated six depth-camera systems across Orbbec, RealSense, Luxonis, and ZED, weighing specular performance, range, width, and software tradeoffs, and chose the Orbbec, which had the best filtering for the bouncing light. Then built the integration: robust bringup of four cameras with a system heartbeat and an auto-recovery system, plus easy toggling and selection of depth streams to manage compute, debugging deep into the Orbbec SDK along the way.

Outcome: four cameras the perception stack could trust. Reliable multi-camera depth that recovered on its own when a camera failed, instead of taking the system down with it.
Four of the depth cameras evaluated: ZED, Luxonis, RealSense, Orbbec, laid out side by side
Four of the systems evaluated: ZED, Luxonis, RealSense, and Orbbec.
Point cloud showing depth camera challenges with specular surfaces
Specular surfaces like glossy plastic and porcelain bounce light and produce wavy point clouds.

3. Optitrack

The problem

Because the arm is built in-house, there were real inconsistencies to chase down. The most notable was droop, where the arm sags from its commanded position under load. For contact cleaning, a few millimeters of error directly hurts clean quality, and it breaks sim-to-real because the model stops matching the physical robot.

What I did

Set up an Optitrack motion capture rig on the arm in-house and connected it to ROS, building out the kinematic chain, a lot of transform and frame math, and URDF cleanup. Profiled droop under different loads, then used the profiles to correct it and update the kinematic model.

Outcome: a corrected kinematic model that improved accuracy everywhere downstream, including simulation. More accurate motion meant more accurate cleaning, and a model that downstream tooling could actually rely on.

Optitrack didn't stay a one-off droop fix. Once the rig was up, it became our ground-truth measurement tool across a whole range of arm, elevator, and base work: end effector and joint errors, controller and tracking accuracy, arm vibration, tower vibration on the elevator, and base drift and repeatability. The board below was our running list of things to measure with it, and the highlighted cells are the ones I dug into.

Top-down view of robot arm with Optitrack tracking markers on each joint
Optitrack markers on each arm joint, tracked in real time.
Project ideas board mapping Optitrack measurements across arm, elevator, and base subsystems
Our running board of Optitrack measurement projects across the arm, elevator, and base. Highlighted cells are the ones I worked on.

4. Other

Manufacturing test suite

The problem

Robots kept breaking down in the field, and diagnosing them remotely was slow. When something went wrong, an operator with no engineering background needed a way to find out what was broken without an engineer on a call.

What I did

Built a manufacturing test suite operators could run as scripts to get a clear report of what was and wasn't working, from the low level (individual arm motors, IDs, baud rates, udev rules) up to full-system checks (complete motion, reported currents, spikes, end-effector behavior), at small, medium, and large scale. Also shipped a simple arm calibration tool so operators could calibrate the arm themselves.

Outcome: operators could self-diagnose a robot in the field. Faster triage of broken robots, and far less dependence on an engineer to figure out what went wrong.
The arm calibration script running (sped up).
Arm before calibration
Before calibration.
Arm after calibration
After calibration.
Tuning controllers to fix an arm vibration issue, the kind of bug you only find on real hardware.

Navigation

Alongside the manipulation work, I spent time on the navigation stack: SLAM mapping with gmapping, testing move_base and then Nav2 with the MPPI controller in simulation and on the real robot, including obstacle-avoidance and wall-following runs. The robot has to get itself to and around the work area reliably before the arm can do anything.

Nav2 testing outside the office.
Obstacle avoidance test (sped up).

Hardware & sensors

A mobile manipulator is many subsystems (arm, base, elevator, scrubber, and a stack of sensors), and a deployed robot only works if all of them do. I did bring-up and debugging across Dynamixel servos, DC and stepper motors, a BLDC base motor, hall sensors (latching and distance), crush sensors, encoders, limit switches, and a magnetometer. Plus low-level system and performance work: a udev-rules wizard to automate device setup, and optimizing Dynamixel syncRead from 15 ms to 10 ms to tighten the arm's control-loop rate.

[INSERT: title for your reflection]

[INSERT: in your own words. What this place meant to you, what you learned, what surprised you about building robots for the real world. This is also where you can acknowledge that Peanut ran out of funding and wound down, an honest, human part of the story.]

Team gathered around fixing broken robot arms together
Arm party: when several arms needed fixing, everyone got together and did it.
C++ Python ROS TOPPRA ros_control ICP / RANSAC Open3D OpenCV Orbbec SDK Optitrack Dynamixel Nav2 Linux