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
Impact and ownership

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.

I owned the arm software and manipulation stack, while working across navigation, hardware, testing and sensor integration to make the complete robot reliable in hotels.

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.

The solution

I built a live trajectory system that let operators guide the arm by hand through a cleaning motion while recording the trajectory. I integrated it with the robot's recovery behaviors, spraying logic and synchronized tool control. The recorded path was time-parameterized with TOPPRA and executed through ros_control.

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.

Outcome and impact

Limitations and improvements

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.

The solution

I built a point cloud registration pipeline to measure how far the toilet pose shifted from a reference bathroom. The system used that offset to reposition the mobile base or shift the arm trajectory. I used passive stereo depth from a ZED X camera for the scan and experimented with Cartesian planning through mission control to apply the correction.

Outcome and impact

One taught trajectory could be adapted to multiple rooms. Instead of re-teaching the complete trajectory in every bathroom, the robot measured the toilet pose offset and corrected the base or arm trajectory.

Limitations and improvements

Registration depended on reliable depth, which was difficult on glossy plastic and porcelain. The depth-camera evaluation and integration below addressed that constraint.

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.

The solution

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 and impact

Added four depth cameras to a robot that previously had none. The integration gave the perception stack simultaneous views around the robot, monitored each camera with a heartbeat, and automatically recovered a camera after a failure.

Limitations and improvements

Four simultaneous depth streams increased compute load, and failures inside the camera SDK were difficult to diagnose. Stream selection reduced unnecessary processing, while the heartbeat and automatic recovery handled dropped cameras.

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.

The solution

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 and impact

A corrected kinematic model improved arm accuracy and simulation fidelity. The OptiTrack rig also became a ground-truth tool for controller tracking, joint and end effector error, vibration, elevator motion, and base drift and repeatability.

Limitations and improvements

OptiTrack was an in-house measurement system rather than an onboard sensor. Its measurements were used to improve the robot's kinematic model and investigate errors that the robot could not measure accurately on its own. The board below lists the follow-up measurements; the highlighted cells are the ones I worked on.

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.

The solution

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 and impact

Operators could diagnose and calibrate robots in the field. The tools reduced triage time and dependence on an engineer for routine diagnosis and arm calibration.

Limitations and improvements

Component-level checks were not enough to capture failures that appeared only during full robot motion. I expanded the suite from individual devices and configuration checks to complete motion, current, spike and end-effector tests at multiple scales.

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.

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