https://drive.google.com/drive/folders/1isZmPOO2c6-z1lyu5qdEl4IOM93BYcho
Team Status Report for 04/25/2026
This week, our team was able to successfully integrate all the live sensors, ESP32 camera stream, and teleoperation onto the catombot (shown in the video link).We were very relieved to get the robot moving by slightly changing the physical design and gear size of the motor’s gear, so it has enough torque to spin the gears and hence the whole bot. Further details are explained in Josh’s status report. These physical improvements, combined with Brooks’s implementation of the motor-subscription logic for the ROS velocity topic, allowed us to verify a full control loop from the base station to the physical robot. Adrian further refined this experience by stabilizing the UWB position stream by implementing a stationary deadband to remove 5-10 cm of jitter and integrating the live ESP32 camera feed into the user interface.
Teleop robot with sensors and a camera with minimal jitter
While teleoperation is functional, the autonomous search algorithm and navigation framework still require significant tuning, specifically regarding IMU calibration to align LiDAR point clouds and prevent drift during goal-seeking. Looking ahead to next week, we will focus on scaling the system by printing and soldering a second robot, refactoring the UI and UWB code to support multi-robot communication, and finalizing the integration of autonomous frontier exploration.
Adrian’s Status Report for 04/25/2026
This week, I improved the live UI’s robot position stability by adding stronger filtering to the UWB position stream. Previously, when the robot was stationary, the displayed x, y values would still drift by 5-10 cm amounts, causing visible jitter that messes up the robot’s UI location on the map. I tightened the stationary deadband so tiny UWB changes are held at the previous position, which makes the bot marker appear much steadier when the physical robot is not moving.
Teleop robot with sensors and a camera with minimal jitter
I also began implementing the autonomous navigation for the robot by adding a live autonomy control path that converts selected goals or frontier targets into geometry_msgs/Twist commands and publishes them to the same ROS topic used by teleoperation (/catombot/id_1/commands/velocity). This means the physical robot can be controlled through the existing firmware path since the ESP32 already subscribes to that velocity topic and applies linear.x / angular.z to the motors. However, I still need to make several fixes, including but not limited to making the IMU have proper calibration so the point clouds are always in the same direction as the robot’s front LiDAR view, and also ensuring the robot doesn’t drift too far off from the target and frontier cells.
Robot moving autonomously (not fully working tho)
For next week, I plan to address those issues as well as refactor the UI to accommodate 2 robots doing the search and creating an alert when the tag is detected.
Adrian’s Status Report for 04/18/2026
This week, I have implemented smoother bot motion on the map and a working camera feed in the user interface. The firmware now filters UWB range measurements per anchor before trilateration, which cuts noise and occasional bad ranges without making the solution sluggish, and I increased how often pose is published so the base station sees fresher samples. On the UI side, I added a light stationary jitter gate so the marker stays put when the robot is actually still, and interpolated pose on the display refresh loop so motion looks continuous between sensor updates instead of stepping at the raw update rate. Together, that gives operators a stable icon when idle and fluid motion when the bot moves.
For the camera, I closed the loop from the robot’s HTTP stream through the bridge into the UI, so the preview panel comes up reliably in live mode, so the practical outcome is that the user can now monitor the scene visually while watching map pose and sensors.

The next step is letting the user work with two bots: choose which one is “live” for teleoperation (commands, camera, and map focus should all follow that selection) and eventually run both in an autonomous frontier exploration mode. That will require a clear notion of active bot identity in application state, namespaced sensor and command topics (or multiplexed streams) per bot in the bridge, and UI affordances so it is obvious which robot you are driving and which feed you are watching. Switching bots should define what happens to in-flight commands and whether camera URLs swap cleanly or need a short loading state.
For autonomy, frontier search should become an explicit mode per bot for teleoperation versus autonomous exploration with a master enable so nothing runs unattended by accident. The existing frontier and occupancy logic can be driven from live pose and LiDAR for the selected bot, with goals or motion commands issued on the same path the user uses for manual control.
Adrian’s Status Report 04/04/2026
This week, I was able to complete the IMU and UWB integrations with the UI by publishing the corresponding ROS topics (/catombot/imu/heading_rad and /catombot/position) to the UI’s live ROS bridge with the micro-ROS agent docker container running. This closes the loop from multi-anchor ranging to something an operator can interpret in real time, which was the main integration milestone.
The remaining issue is the quality of the pose, not whether it appears on the screen. Raw UWB fixes are noisy as the bot’s positions appear jittery on the UI, so the next steps would be to have sensor fusion properly implemented. For UWB + IMU fusion, the IMU will be used for acceleration, which is integrated into position while letting UWB correct drift and anchor the global position. This can be done with a Kalman filter that can sooth UWB updates and uses IMU between fixes so the displayed pose and mapping see less high-frequency noise.

UI displays the x, y, z coordinates of the bot/UWB’s location with respect to the 3 UWB anchors surrounding it.

The UI records and properly updates the bot’s position based on the latest coordinate recorded by the UWB. It does, however, vibrate despite the UWB being stationary. Hence, sensor fusion with Kalman filtering is needed.
Team’s Status Report for 03/28/2026
Risks:
There are no major new risks at the moment. We are proactively mitigating the primary risks associated with full robot assembly by continuing our strategy of modular integration testing. For example, this week we successfully wired up both the IMU and the TF-Luna LiDAR to run simultaneously on the ESP32-S3 using the same shared I2C GPIO pins. This important step confirms that our data interconnects and initial module interactions are stable before committing to final assembly. Looking ahead, we still need to begin soldering components directly to the PCBs and attach the buck converters to the remaining battery connectors. Therefore, our main remaining risk is the potential for unforeseen hardware or power distribution issues arising during this final, permanent physical integration phase.
Design and Schedule Changes:
There have been no fundamental changes to our overall design path or timeline. However, we did introduce a highly effective architectural enhancement: utilizing a micro-ROS middleware layer encapsulated in Docker to better bridge the raw, hardware-level sensor data with the live UI. This architectural enhancement solved a difficult integration problem without altering our core design. We remain on schedule overall; while UWB trilateration is still being finalized, we have successfully gotten the motors, H-Bridge, IMU, and LiDAR operational.
Adrian’s Status Report for 03/28/2026
The primary focus this week was bridging the gap between hardware-level sensor data and the live UI by getting real-time data to at least show up there rather than just the terminal/Arduino output. This involved stabilizing the ESP32-S3 firmware for distance sensing and implementing a robust micro-ROS middleware layer using Docker to ensure the frontend can reliably consume live LiDAR feeds without direct serial dependencies.

The metric on the bottom left represents the distance between the LiDAR and iPad, where the occupied cell is marked at around 30 cm. ESP32-S3 firmware (PlatformIO) reads a TF-Luna over I2C and publishes distance on micro-ROS (/catombot/lidar/front/distance_cm). Then the UI repo documents a live LiDAR bridge: micro-ROS agent in Docker, optional npm run bridge:lidar with ROS topic echo → Server-Sent Events, or --source=serial using PlatformIO serial monitor parsing. The UI can be opened in live mode (?lidar=live and related env vars).

A small read_serial.py script also helps validate raw serial timing and baud settings on macOS/Linux.

Additionally, micro-ROS Docker container was especially valuable because it turned a difficult hardware-to-browser integration problem into a much more manageable interface problem. Instead of trying to make the frontend understand ROS-native or serial-native behavior directly, the container provided a stable middleware layer that could ingest the live sensor feed, preserve useful metadata, and expose it in a form the UI could actually consume reliably.
The next steps are to calibrate LiDAR so ranges and geometry match the real map scale and robot frame, and to fuse IMU data into pose and speed estimates so on-screen motion reflects how the CatomBot actually moves. Depending on progress with UWB range triangulation, I will also surface that data in the UI (e.g. anchors and fused position) and use it to tighten FREE vs OCCUPIED classification and stabilize frontier cluster centroids that guide exploration
Adrian’s Status Report for 03/21/2026
My plan this week was to wire Vector Field Histogram (VFH) into the live navigation loop and make frontier-based exploration visible on the tactical map so the operator can see both where coverage is growing and where the swarm is pushing next.
Across the four images, exploration grows from a small green pocket with a partial red wall outline to a fully mapped space whose OCCUPIED boundary cells form a complete red perimeter where LiDAR marks FREE interior and stamps OCCUPIED where rays hit the map edge. Teal trails stay in free space and skirt those red cells because VFH turns the global heading toward each frontier centroid into a locally safe steering angle via a polar obstacle histogram, then kinematics plus grid collision keep bots out of walls. The heatmap blooms track visit density and pile up at the leading edge of the dark UNKNOWN region, matching frontier detection (FREE cells next to UNKNOWN cells); as the frontier collapses, the map fills with green and the bright coverage fades, showing frontier-following exploration with obstacle-aware motion end to end. Once the entire map is covered with labeled/colored cells, the bots return to the base station without hitting OCCUPIED cells along the way.




VFH is implemented in src/exploration/vfh.ts, which builds a 72-sector polar obstacle-density histogram from nearby OCCUPIED cells, thresholds sectors into a blocked mask, finds contiguous free “valleys,” and picks a steering heading that stays aligned with the global goal while avoiding obstacles. The simulator (mockDataSimulator.ts) now uses this on every step: for each bot, it computes the heading toward the assigned frontier centroid, runs computeVfhHeading, then projects a short virtual waypoint along vfh.steeringHeading so the existing proportional kinematics controller tracks a VFH-safe direction. Map polygon bounds and grid-based collision resolution still apply after the move, so local avoidance and global feasibility stay consistent. Unit tests in vfh.test.ts cover histogram construction, valley finding, and end-to-end computeVfhHeading behavior.

Note that currently, the full histogram is not drawn as a separate UI panel; it powers motion in the sim. Exposing a small polar plot or debug overlay would be optional follow-on work.
Frontier logic is unchanged in principle, but the store now carries explorationGrid and frontierClusters each tick, and MapCanvas.tsx layers them so the algorithm is visible:
Status & next steps
Integration is on track: VFH is in the control loop, frontiers are fed from the same grid the sim uses, and the map reflects both. Next: optional debug visualization for the live histogram, tighter tests between VFH + multi-bot assignment under clutter, and (if needed) performance tuning of sector count / active radius for the target update rate.
Team’s Status Report for 03/14/2026
Design State:
The main thing completed this week was the transition from theoretical design to physical assembly and software modularity. Adrian rebuilt the navigation logic into a testable, frontier-based 2D simulation stack (handling LiDAR, frontiers, target selection, and navigation), replacing the older BFS algorithm.
On the communications side, Brooks set up microROS and ROS2, demonstrating that we can publish data from the ESP32S3 to a ROS agent over WiFi. He also booted the new Qorvo DWM3001CDK UWB modules using the “UCI” firmware, confirming our approach to Two-Way Ranging. With the arrival of almost all our parts, Josh began physical hardware integration, mapping out connections and crimping battery connectors to prepare for full assembly.
Risks:
We encountered a hardware risk: upon inspection, Josh realized the chosen H-Bridge requires a 5V control input, making it incompatible with our 3.3V ESP32. We are mitigating this by pivoting to a new component. Fortunately, this specific setback does not immediately impact the critical path, as there is sufficient other wiring and driver development to complete while we wait for the replacement.
Schedule Changes:
Despite the strain on the hardware integration path and the H-Bridge pivot, progress remains steady and on schedule.
Adrian’s Status Report for 03/14/2026
This week, I shifted focus from the visual dashboard to the exploration logic, building out the 2D simulation and test suite that will eventually drive real bot behavior. The goal was to extract the navigation brain from the simulator written before into testable modules and to validate that the full pipeline (sense → reason → move) actually produces growing map coverage in the environment.
The simulator’s BFS coverage loop was replaced with a proper frontier-based exploration stack, split into four independent modules:
lidar.ts: Simulates a 2D LiDAR scan from a bot’s current position. Casts 72 rays at 0.35m increments out to an 11m range. Each ray marks traversed cells FREE in the occupancy grid and stamps the last in-map cell OCCUPIED when the ray exits the map boundary, giving the grid a sense of walls.
frontiers.ts: A frontier is any FREE cell that has at least one UNKNOWN 4-connected neighbor still inside the map boundary, which represents the edge of explored space. This module detects all frontier cells, then groups them into 8-connected clusters via BFS (sorted largest first). It also computes an information gain score for each cluster by simulating a read-only LiDAR cast from the cluster centroid and counting how many unseen UNKNOWN cells would be revealed if a bot navigated there. This drives the “go where you’ll learn the most” behavior.
targetSelection.ts: The assignment layer. Each bot is scored against every eligible frontier cluster using the formula distance / (1 + informationGain), which naturally biases bots toward high-yield frontiers nearby. A shared claim map prevents two bots from targeting the same centroid. The swarm-level assignFrontiersToBots function sorts clusters by information gain descending, then greedily assigns the nearest unassigned bot to each, ensuring coordinated coverage without communication overhead.
navigation.ts: A proportional heading controller. Given a bot’s current pose (x, y, θ) and a target position, it computes a new heading and position for one timestep with a turn rate cap. Collision resolution is handled by the existing resolveMapCollision utility from mapLayout.ts.
Test Suite Execution
Terminal output from npm run test showing 7 test files (24 tests) passing in 483ms: mapLayout, frontiers, integration, lidar, navigation, smoke, and targetSelection.
Module High-level Architecture
The diagram shows how each test suite maps directly to its extracted module, all of which feed into the mockDataSimulator. The simulator is now a thin consumer of these modules rather than the place where logic lives, making each concern independently tunable.
Progress is currently on schedule. Next week: wire the frontier modules into the live simulator, begin the WebSocket telemetry bridge, and add CI to run the test suite on every push. Also, start planning to integrate the Vector Field Histogram for real-time local obstacle avoidance.
