Project portfolio by Tyler Lum and Michael Milner documenting the design, implementation, and testing of an autonomous Romi robot developed to complete a multi-stage obstacle course using line sensors, encoders, bump switches, an IMU, and observer-based state estimation.
This project focused on the design and implementation of an autonomous Romi robot capable of completing a structured obstacle course without direct human control. The goal was not just to make the robot move, but to build a system that could sense its environment, interpret what was happening, and respond in a reliable and repeatable way.
To accomplish this, the robot combines mechanical design, embedded systems, sensor integration, and control algorithms. The final system uses a reflectance sensor array to detect and follow a printed line, wheel encoders to measure wheel motion, front-mounted bump switches to detect wall contact, and an inertial measurement unit (IMU) to measure heading and angular velocity. These measurements are combined in software using both direct feedback control and a discrete-time observer.
One of the most important parts of this project was the software structure. Rather than writing one large control loop, the robot was organized into multiple cooperating tasks. Separate tasks were used for motor control, line following, IMU processing, observer estimation, bump detection, garbage collection, and user interaction. This made the system easier to debug, easier to expand, and much closer to how a real embedded robotic system is structured.
A major theme throughout the project was balancing theory with real-world behavior. On paper, encoder counts, heading measurements, and controller equations can look straightforward. In practice, sensor noise, timing, actuator mismatch, and memory limits all affect performance. This project ended up being a strong lesson in how important integration, tuning, and iteration are when building an actual autonomous system.
The main objective of this project was to develop a robot that could complete the required obstacle course autonomously while demonstrating good engineering design, not just isolated functionality. That meant the robot needed to complete multiple behaviors in sequence and transition between them in a predictable way.
In the end, the goal of the project was not only to produce a functioning robot, but to demonstrate a full engineering process: system design, implementation, testing, debugging, and reflection.
Before implementing the final robot behaviors, it was important to understand the underlying Romi platform and the physical parameters that affect motion, sensing, and control. The Romi chassis uses a differential-drive layout, meaning motion is generated by independently controlling the two wheel speeds. Key geometric and encoder parameters directly influence distance resolution, turning behavior, and observer modeling.
| Parameter | Value |
|---|---|
| Chassis Diameter | 165 mm |
| Track Width (wheel center to wheel center) | 141 mm |
| Wheel Radius | 35 mm |
| Wheel Circumference | 219.911 mm |
| Gear Ratio | 120:1 |
| Encoder Resolution at Motor | 12 counts/rev |
| Effective Encoder Resolution | 1440 counts/rev |
| Rated Motor Voltage | 4.5 V |
| Motor Stall Torque | 25 oz-in (approximately 0.1765 N·m) |
| Motor Stall Current | 1.25 A |
| No-Load Motor Speed | 150 rpm at 4.5 V |
| Maximum Translational Speed | 0.55 m/s |
| Theoretical Max Speed (6×AA NiMH, 7.2 V) | 0.88 m/s |
| Theoretical Max Speed (6×AA Alkaline, 9.0 V) | 1.10 m/s |
| Alkaline vs NiMH Speed Increase | 25% |
These values were especially useful for estimating encoder distance resolution, expected robot speed, and the scale of the physical model used in the observer.
Because each wheel revolution corresponds to 1440 encoder counts and the wheel circumference is about 0.2199 m, the robot can theoretically resolve straight-line travel on the order of 0.153 mm per encoder count. This gave the control and estimation software a solid measurement foundation, provided wheel slip remained limited.
The Romi is also nonholonomic, meaning it cannot move sideways directly. Instead, all path changes must be generated by the relative speeds of the two wheels. That made wheel-speed regulation, heading estimation, and line-sensor feedback especially important in practice.
This section provides a step-by-step guide for assembling the Romi robot, including mechanical structure, electronics integration, and sensor installation. We wanted the build sequence to be straightforward enough that another student could follow it without having to undo major steps later.
Before beginning assembly, verify that all required hardware components, cables, and tools are available. This includes standoffs, screws, the Romi chassis, Nucleo board, Shoe of Brian, IMU, and pre-crimped jumper wires. Having all components ready prevents interruptions during assembly.
Attach four 30 mm standoffs to the Romi chassis using nylon lock nuts. These standoffs form the vertical support for the electronics stack. Ensure they are aligned properly in the mounting slots and do not overtighten, as the plastic chassis can be damaged.
Create and organize the required cable assemblies, including power, motor, and encoder cables. Follow proper color conventions and confirm pin orientations before connecting anything. Route the cables through the center of the chassis to simplify later connections.
Mount the Romi-to-Shoe adapter plate onto the standoffs using M2.5 screws. Feed motor and encoder wires through the center cutout to keep wiring clean and organized.
Install four 8 mm standoffs onto the adapter plate, then attach the Shoe of Brian using screws and nylon washers. Afterward, carefully mount the Nucleo L476RG onto the Shoe of Brian, ensuring proper alignment of the header pins.
Connect all cables from the Romi power distribution board to the Nucleo. This includes power, motor control signals, and encoder inputs. We treated this as one of the most important steps in the whole build because incorrect connections could damage the microcontroller immediately.
Mount the BNO055 IMU using standoffs and screws on the Romi chassis. Position it to avoid interference with other components and ensure stable orientation for accurate heading and angular velocity measurements.
Mount the Pololu QTRX-A reflectance sensor array at the front of the robot using four 1/4" 2-56 MF standoffs, 2-56 screws, and nuts. This sensor detects the line by measuring reflected infrared light and enables real-time steering corrections.
Mount two snap-action bump switches with 18.5 mm lever arms at the front of the robot, positioned in front of the line sensor. These were secured using hot glue to the frame, which ended up being a simple and effective method for detecting wall contact during the obstacle course.
Following this build sequence helped keep the robot assembly organized and made later debugging much easier.
Because this project integrated motors, encoders, line sensors, bump switches, and an IMU onto a single microcontroller, documenting the wiring clearly was an important part of the final package. A full wiring diagram was created so the robot could be debugged, understood, and reproduced more easily by another student or team.
The full wiring diagram for the Romi robot electrical system is shown below.
Figure 1. Full wiring diagram showing MCU, motor, encoder, IMU, line sensor, and bump switch connections.
The table below summarizes the main electrical connections used in the final robot system based on the final wiring layout.
| Subsystem | Signal / Wire | Nucleo Pin | Notes |
|---|---|---|---|
| Romi PDB | GND (black) | GND | Common ground connection |
| Romi PDB | VSW (red) | Vin | Battery / switched supply path |
| Left Encoder | ELA (blue) | PA_8, PWM1/1 | Timer encoder input |
| Left Encoder | ELB (yellow) | PA_9, PWM1/2 | Timer encoder input |
| Right Encoder | ERA (blue) | PA_0, PWM2/1 | Timer encoder input |
| Right Encoder | ERB (yellow) | PA_1, PWM2/2 | Timer encoder input |
| Left Motor | PWM (green) | PB_1 | Motor PWM command |
| Left Motor | DIR (yellow) | PB_15 | Direction control |
| Left Motor | SLP (blue) | PB_14 | Sleep / enable control |
| Right Motor | PWM (green) | PB_0 | Motor PWM command |
| Right Motor | DIR (blue) | PB_5 | Direction control |
| Right Motor | SLP (yellow) | PB_4 | Sleep / enable control |
| IMU | 3.3V (red) | 3V3 | Power |
| IMU | GND (black) | GND | Ground |
| IMU | Grey | PB9 | I2C line |
| IMU | Purple | PB8 | I2C line |
| IMU | White | PC9 | Optional / auxiliary line |
| Bump Sensor (right) | COM | GND | Common terminal |
| Bump Sensor (right) | NO | PB7 | Normally open input |
| Bump Sensor (left) | COM | GND | Common terminal |
| Bump Sensor (left) | NO | PB2 | Normally open input |
| Line Sensor Signal | Nucleo Pin |
|---|---|
| GND | GND (CN7) |
| VCC | 3V3 (CN6) |
| CTRL | PC10 |
| 1 | PC0 |
| 3 | PC1 |
| 5 | PA5 |
| 7 | PA6 |
| 9 | PA7 |
| 11 | PC2 |
| 13 | PC5 |
| 15 | PC4 |
This table was included so that the physical robot and its software pin assignments can be cross-referenced more easily during debugging and future reproduction.
The robot is built on the Romi differential-drive chassis, which uses two independently driven wheels and a passive rear support. This configuration is simple, compact, and especially well suited for line-following and small navigation tasks because translational and rotational motion can be controlled through the relative wheel speeds.
The mechanical design of this project was driven less by large custom structures and more by thoughtful sensor placement. Since the robot depends heavily on what it can sense, where the sensors are mounted has a direct effect on performance. The line sensor array, bump switches, and IMU were all positioned with a specific purpose in mind.
The primary line-detection hardware is a Pololu QTRX-A reflectance sensor array mounted underneath the front portion of the robot. This array was placed forward of the wheel axis so the robot can “see” the line before the center of the chassis passes over it. That matters because it gives the controller more time to react. If the line sensor were directly under the center of the robot, the steering correction would happen later and the robot would be more likely to overshoot or oscillate.
Keeping the array low and level relative to the course surface was also important. Reflectance sensors are sensitive to height, so any tilt or uneven mounting can change the magnitude of the readings. A stable mounting position helps the calibration stay meaningful and improves consistency from run to run.
The robot uses two Pololu snap-action switches with bump levers mounted at the front of the chassis. These act as physical contact sensors. The reason this was a good choice for the obstacle portion of the course is that the required behavior was based on actual impact with a wall, not simply detecting something nearby. A mechanical switch provides a clear and unambiguous signal: either contact happened or it did not.
The front-facing placement also increases reliability. Since both switches are near the forward edge of the robot, wall contact is detected immediately when the robot reaches the obstacle. Using two switches also improves robustness in cases where the robot approaches slightly off-center.
The BNO055 IMU was mounted near the center of the robot. This location was chosen to reduce the effect of rotational offset and vibration on the measured signals. Since the IMU is used to estimate heading and yaw rate, placing it near the center of rotation makes those readings more representative of the robot as a whole.
Although the line sensors and encoders do most of the direct navigation work, the IMU adds another layer of information about how the robot is moving. That made it valuable not only for real-time use, but also for debugging and analyzing the robot’s dynamic behavior during turns.
The electrical system is centered around an STM32 microcontroller running MicroPython. The microcontroller interfaces with several kinds of hardware at once, each with different signal requirements.
The bump switches are configured so that they normally read HIGH and transition to LOW when pressed. This makes them easy to handle in software because the code can detect the falling edge and immediately trigger a stop condition. The line sensor emitter is also controlled directly in software, which allows the system to manage the reflectance readings in a more controlled way.
Overall, the electrical design was aimed at reliability and clarity. A robot like this is only as good as its sensing and actuation pathways, so making sure the hardware connections were stable and logically organized was just as important as the code itself. As part of the final documentation package, we also created a full wiring diagram for the robot so that the system could be more easily understood, debugged, and reproduced by another student or project team.
For reproducibility, the repository includes the source code, the portfolio site, documentation pages, the wiring diagram, calibration files, and supporting media. Our project did not rely on large custom-manufactured parts, but we did document the mounting approach, sensor placement, hardware stackup, and wiring so that the robot could be rebuilt with standard hardware and the same electrical layout.
Figure 2. Fully assembled Romi robot showing the final integrated hardware layout.
In addition to the final demonstration, we included a few representative plots from testing and tuning. These helped us understand how the robot was behaving in real time and allowed us to make more informed adjustments instead of relying on trial-and-error.
This plot shows the weighted centroid signal used by the line-follow controller over time. The centroid represents where the robot “thinks” the line is relative to its center. When the values stay near the desired range, the robot is tracking the line well. Larger deviations indicate moments where the robot drifted, encountered curvature, or had to correct its path. This plot was especially useful for tuning base speed and controller gains while also revealing how much noise was present in the sensor readings.
Figure 3. Line-follow centroid over time during a representative run.
This plot shows the estimated x-y trajectory of the robot based on the state estimator. Instead of just reacting to sensor inputs, the robot continuously estimated its position using encoder and IMU data. The shape of the path gives a visual sense of how the robot moved through the course and whether the estimator was producing reasonable results. While some drift is expected without absolute position feedback, the overall motion trends helped validate the observer design.
Figure 4. Estimated x-y path reconstructed from the state estimator.
This plot shows the closed-loop response of the motor velocity controller to a step input. It was used to tune the PI controller gains by observing overshoot, settling time, and steady-state behavior. A well-tuned response reaches the desired speed quickly without excessive oscillation. This confirmed that the motor control was operating in closed-loop and that improvements were based on measured system performance rather than guesswork.
Figure 5. Closed-loop motor step response used for PI tuning.
The software is organized as a cooperative multitasking system. Instead of trying to run everything inside one giant while loop, the project uses individual tasks that are scheduled periodically. This makes the overall system much easier to reason about and debug, because each task has a clear purpose and a bounded responsibility.
The scheduling infrastructure is based on generator-style tasks that yield control back to the scheduler after each run. The task framework also supports task priorities, timing periods, and profiling. In the final system, the bump task runs at the highest priority and a 5 ms period, the motor tasks also run every 5 ms, the line-follow task runs every 10 ms, and the IMU, observer, and user interface tasks run every 20 ms. Garbage collection runs in the background on a slower 100 ms schedule.
Because the system is split into multiple tasks, data has to move safely between them. That is handled through Shares and Queues. Shares are used for single live values such as motor enable flags, velocity setpoints, measured states, and estimated observer states. Queues are used for buffered logging and time histories. This makes the communication between tasks organized and much safer than directly letting multiple pieces of code overwrite each other’s variables.
The main program initializes the motor drivers, encoder timers, line sensor array, IMU, serial interface, Shares, and Queues. It then creates the major tasks and adds them to the scheduler. In practical terms, the main file acts like the system integration layer: it does not contain all the control logic itself, but it instantiates all of the modules that together make the robot operate.
Each motor is controlled by its own task. The motor task uses encoder feedback and PI control to regulate wheel motion. It also computes and publishes useful values for other parts of the system, including motor input voltage and wheel displacement in meters. One especially useful implementation detail is that encoder updates continue even when the motor is not actively commanded to move, so the observer still has current displacement information available.
The PI control approach works by comparing the desired wheel velocity to the measured velocity and then using proportional and integral action to reduce the error. This helps the robot maintain more consistent motion even when the motors are imperfect or the battery voltage varies.
The line-following algorithm uses a centroid-based method. The sensor array readings are normalized, and the system computes the effective line position across the width of the sensor array. That position becomes the error signal for the line-follow controller.
The line-follow task uses proportional, integral, and derivative terms to compute a steering correction. The controller then modifies the left and right wheel velocity commands relative to a base speed. In other words, the robot does not simply “turn left” or “turn right” in a binary way; it continuously adjusts its wheel commands to stay centered on the line. The implementation also includes smoothing, deadband behavior, and logic for weak line signals so that the robot does not instantly spin when the line becomes temporarily unclear.
The IMU task is responsible for reading heading and yaw rate from the BNO055 and publishing those values for use elsewhere in the system. One subtle but important part of this implementation is that heading is unwrapped. Since headings naturally jump from 359 degrees back to 0 degrees, using the raw wrapped angle can create artificial discontinuities. The IMU task avoids that problem by maintaining a continuous representation of heading.
The IMU task also supports a non-blocking calibration flow. Through the user interface, the system can request calibration, attempt to load saved calibration values, wait for sufficient sensor calibration, and then save the updated calibration to a file. This made the robot much more practical to operate and re-use between runs. The task also includes filtering and spike rejection to keep unrealistic yaw-rate values from corrupting the measurements.
One of the strongest parts of this project is the observer-based state estimation. Instead of relying purely on raw measurements, the robot uses an observer task that combines motor inputs, encoder-based displacement measurements, and IMU data to estimate the internal state of the system.
The estimated states include:
sψωLωR
The observer runs at 20 ms and uses matrices defined in observer_matrices.py. The uploaded matrices show a discrete-time model with a sample time of 0.02 s and a 4-state formulation. The file comments also show the physical parameters used in the model, including wheel radius and wheelbase-related terms. That means this is not just a conceptual observer; it is grounded in the physical robot model.
From a portfolio standpoint, this matters a lot because it shows that the project goes beyond basic line following. The robot is not just reacting directly to sensor values; it is estimating its motion using a state-space framework. That is a much more advanced approach and demonstrates a strong connection between lecture theory and real implementation.
The bump task continuously monitors the two front switches. It uses edge detection to identify a new press event, and when either bump switch transitions from unpressed to pressed, the task immediately disables both motors and disables line following. This was a good design choice because it made the collision response decisive and easy to understand. Rather than letting multiple tasks argue over what to do after impact, the bump task forces the system into a safe state as soon as contact is detected.
The user interface task is another major strength of the project. Through the serial interface, the user can print the help menu, update gains, set the line-follow base speed, calibrate the line sensors on white and black surfaces, print sensor data, start line following, stop the system, trigger IMU calibration, save calibration data, and inspect observer values. This made testing and debugging much more efficient, because it removed the need to constantly hard-code parameters during development.
One of the biggest lessons from the software side of this project was how much easier it is to manage a complicated robot when the code is modular. At first, it is tempting to write everything in one place because it feels faster. But once multiple sensors and behaviors are involved, that approach becomes hard to maintain very quickly. Splitting the system into tasks made the code cleaner, but more importantly, it made the robot easier to understand during debugging.
We also found that the hardest part of the project was not getting one subsystem to work by itself. The hardest part was getting everything to work together consistently on the actual robot. A lot of what looked straightforward in theory still needed tuning, testing, and a lot of iteration once the whole system was running at the same time.
Line Sensor → task_line.py → velocity commands → task_motor.py → motor driver → wheels
Encoders + IMU → task_observer.py → estimated state variables
Bump switches → task_bump.py → stop condition and safety response
One of the biggest goals of this project was to keep the software organized in a way that made the robot easier to test, debug, and improve over time. Instead of writing one large script that handled everything at once, the code was broken into multiple files with clearly defined roles. This made the system much more manageable, especially once the project grew to include line sensing, motor control, bump detection, IMU processing, observer estimation, logging, and a user interface.
At a high level, the software is built around a cooperative multitasking architecture. The main.py file initializes the hardware, creates the task objects, allocates shared variables and queues, and registers everything with the scheduler. Each task then runs periodically and focuses on one specific job, such as controlling a motor, reading the IMU, following the line, or handling bump events. This structure made the project feel much more like a real embedded robotic system rather than a collection of disconnected lab scripts.
main.py
The main file is the center of the entire project. It initializes the PWM timer for the motors, configures two encoder timers, creates the line sensor array, starts I2C communication for the BNO055 IMU, and sets up a shared UART channel for estimator streaming. It also creates all of the Shares and Queues used by the tasks, including motor enable flags, line-follow setpoints, observer values, IMU values, and logging buffers. After that, it instantiates the task objects and adds them to the scheduler. In other words, main.py is the system integration layer that ties the whole robot together.
The task files are the heart of the robot behavior. Each one is responsible for a specific subsystem and is designed to run repeatedly under the cooperative scheduler.
task_motor.pyThis file implements the motor-control task for each wheel. It combines encoder updates with PI velocity control and also publishes useful observer signals such as motor input voltage and wheel displacement in meters. One especially good design choice in this file is that encoder position continues to update even when the motor is not actively being commanded to run. That means the observer can still track wheel displacement if the robot is moved by hand or if the control state changes. Overall, this file handles the low-level closed-loop actuation that makes the rest of the robot possible.
task_line.pyThis file contains the line-following outer-loop controller. It uses a centroid-based line detection method and writes left and right velocity setpoints whenever line following is enabled. The task uses proportional, integral, and derivative terms and includes several practical improvements for weak line signals, such as a smaller denominator floor, realistic confidence thresholds, and logic to hold the last valid position when the line signal becomes poor instead of immediately commanding an aggressive spin.
task_imu.pyThis file is responsible for acquiring and conditioning the IMU measurements. It reads heading and yaw rate from the BNO055, converts values into useful forms, and publishes them to Shares for use by the rest of the system. Its most important features include heading unwrapping, filtering, spike rejection, calibration loading, and calibration saving.
task_observer.pyThis file implements the observer task. It takes in motor voltages, measured wheel displacements, heading, and yaw rate, and uses a discrete-time state-space model to estimate the robot state. This is one of the strongest technical features of the entire project because it connects the final implementation directly to controls theory and state estimation.
task_bump.pyThis file handles collision detection using the left and right snap-action bump switches. It uses edge detection to identify a new press event and immediately disables the motors and line-following mode when contact occurs.
task_user.pyThis file implements the serial user interface. It allows real-time parameter tuning, sensor calibration, command entry, and observer inspection, which made debugging and iteration much faster.
task_log_est.pyThis file logs observer estimates over time and computes x-y dead-reckoned position for analysis. It is especially useful for validating the observer and examining robot motion outside the live demo.
task_garbage.pyThis file periodically runs garbage collection in the background. Even though it is simple, it plays a practical role by helping manage memory on the microcontroller.
motor_driver.pyThis file defines the motor driver class and abstracts the PWM, direction, and enable logic used to command the motors.
encoder.pyThis file defines the encoder class used to read cumulative wheel position and velocity. It also handles timer overflow and timing calculations.
line_sensor.py
This file defines the LineSensorArray class. It manages ADC reads, emitter control, oversampling, ambient cancellation, and white/black calibration for the Pololu line sensor array.
driver.pyThis file contains the low-level BNO055 IMU driver. It handles I2C communication, chip identification, mode setting, raw sensor reads, and calibration data handling.
task_share.pyThis file provides the shared-data infrastructure used throughout the project. It defines Shares and Queues, which are essential for clean communication between the tasks.
cotask.pyThis file implements the cooperative scheduler itself. It defines the task objects, run timing behavior, priorities, and profiling support that make the multitasking system possible.
observer_matrices.pyThis file stores the discrete-time observer matrices used by the observer task. It directly represents the mathematical model of the robot.
boot.pyThis file is the standard MicroPython boot file. It is mostly a support file, but it still forms part of the full software package.
The hardware interface classes provide access to the motors, encoders, line sensors, and IMU. The task infrastructure files provide the scheduler and communication tools. The task files sit on top of those layers and implement the actual robot behavior: reading sensors, estimating states, controlling motion, detecting events, and responding to user commands. Finally, the support files such as the observer matrix file and boot file complete the overall software package.
The files below link to dedicated documentation pages for each source file. Each documentation page explains the file’s role in the project, important classes or tasks, key variables, and how that file interacts with the rest of the software system. A second link is also provided to open the original source code directly. CLICK ON THE BLUE BUBBLES THEMSELVES TO OPEN EACH SPECIFIC PAGE. EX: Click on main.py bubble
Some of the most important parts of the project do not fit naturally inside a single source file, so this section is meant to highlight the system-level pieces that connect everything together. This includes the overall task structure and the high-level sequence of course behaviors.
A task interaction diagram is useful because it shows how the major software modules communicate through Shares and Queues. This helps explain how the line-following, motor-control, IMU, observer, bump, and user-interface tasks are related at the system level instead of only as individual files.
Figure 6. High-level task diagram showing communication between the major software modules.
The obstacle course behavior was implemented as a sequence of states rather than one continuous block of control logic. A state transition diagram makes it easier to explain how the robot moves between line following, straight driving, turning, bump detection, and restart behavior.
Figure 7. High-level state transition diagram for the course logic. Related states are grouped into phases.
One thing we realized during the project is that code alone does not always communicate the full design very well. The diagrams help bridge the gap between the implementation and the bigger system-level thinking behind it. They also make the project easier for someone else to understand quickly, especially if they want to rebuild or extend it later.
By the final stages of the project, the robot was able to demonstrate stable line following, reliable bump detection, and a well-structured software workflow for tuning and testing. The system showed that the mechanical, electrical, and software components were working together as an integrated whole rather than as isolated parts.
The line-following performance improved significantly once the sensor calibration and control gains were tuned. The centroid-based method gave the robot much smoother behavior than a simple threshold-based approach would have. Instead of abruptly reacting to a single sensor crossing a threshold, the robot could respond continuously based on where the line was located relative to the array.
Another important improvement came from the weak-signal handling logic. Real line sensors are never perfectly clean, and there were times when the line signal became less distinct. The code’s decision to hold the last valid line position when the signal confidence dropped helped avoid sudden, unrealistic spins and made the line-follow behavior much more stable.
The encoder-based PI motor control improved repeatability by reducing the dependence on open-loop timing. Without feedback, even small changes in battery level or friction would have changed the robot’s motion noticeably. With feedback, the robot could regulate its wheel behavior more consistently, which was especially useful when transitioning between behaviors or trying to maintain stable line-follow speed.
The observer added real value to the project because it gave a cleaner and more structured estimate of the robot’s motion than raw measurements alone. Even when not used to fully close the loop on every behavior, it provided strong visibility into what the robot was doing internally. That made it useful both as a control-related feature and as a diagnostic tool.
More importantly, implementing the observer showed that the project moved beyond surface-level autonomy. It connected the robot to formal modeling concepts from class and demonstrated that the system could estimate position, heading, and wheel speeds using a state-space framework.
Probably the most valuable lesson from this project was that successful robotics work is mostly about integration. It is one thing to make a line sensor work on the bench or to make an encoder report counts correctly. It is another thing to combine all of those pieces into a robot that behaves predictably in real time.
This project also reinforced how important testing and iteration are. A lot of the final performance came from repeatedly running the system, observing what went wrong, and refining both the hardware setup and the software logic. In that sense, the final robot reflects not just the first design idea, but the full debugging and refinement process behind it.
This section provides visual documentation of the completed robot hardware. These images show the final integrated build, sensor placement, and assembly details that support the written design discussion.
Figure 8. Overall view of the fully assembled Romi robot.
Figure 9. Underside view showing the line sensor mounting location.
Figure 10. Front hardware view showing bump-switch placement.
Figure 11. Additional hardware view of the completed robot assembly.
A demonstration video is included here to show the robot operating at its best level of performance. The video demonstrates line following, obstacle interaction, and the full autonomous behavior sequence.
Or, open it directly:
Watch the Demonstration VideoThe full project repository contains the source code, supporting documentation, and design materials used in this project. The goal of the repository is not just to store code, but to make the full engineering process understandable and reproducible. Someone looking through the repository should be able to follow how the robot was built, how the software is organized, and how the system was tested and demonstrated.
In addition to the Python source files, the repository includes the portfolio website, documentation pages, wiring information, calibration files, images, and supporting material used to explain the project. Because the robot mainly uses standard hardware and direct mounting methods rather than large custom manufactured parts, the emphasis was placed on clearly documenting the mounting approach, sensor locations, electrical connections, and software structure.
These are just a few of the main documentation pages. For a more complete breakdown of every file, use the Detailed Code Documentation section above.
To make the project reproducible, the repository includes the portfolio website, documentation pages, source code, figures, calibration files, and supporting robot software files needed to understand the full system design and implementation. Our intention was that another student could use the repository not only to read about the project, but also to understand how to rebuild the robot and how the software pieces work together.
For external reference related to the project and course support, the following contact is included: