Monday, May 5, 2025

PolyMap & MinOne - MinOne gets Log-Odds (#5)

Improving Minone and PolyMap with Log-Odds

I have recently upgraded Minone and the PolyMap mapping system, focusing particularly on enhancing our use of a Single Ultrasonic Sensor. While the sensor hardware remains unchanged, introducing the concept of "log-odds" has significantly reduced the impact of noise and false readings, making our maps clearer and more accurate.

What are Log-Odds? (Explained Simply)

Imagine you're guessing whether it will rain today. You might say there's a 50/50 chance—that's "even odds." Now, suppose new clouds roll in, and your chances become "likely" rain, maybe 75%. Each time you get new information, your confidence about rain happening or not happening changes.

Log-odds is just a special way of keeping track of these changing guesses. It turns probability—the chance something is true—into a number that's easier to update quickly. Positive log-odds numbers mean "more likely," negative log-odds numbers mean "less likely," and zero means completely uncertain.

Occupancy Grids - Binary vs Log-Odds

Applying Log-Odds in PolyMap's Occupancy Grids

In mapping, especially with sensors like the ultrasonic sensor used in Minone, each measurement tells us something about whether space is empty or occupied. However, sensors aren't perfect; sometimes they say there's something there when there isn't (false positive) or fail to detect something that really is there (false negative).

Using log-odds, The system doesn't just accept each measurement blindly. Instead, it builds confidence over multiple readings. Every time the ultrasonic sensor scans an area:

  • A positive reading increases the log-odds, suggesting the area might be occupied.

  • A negative reading decreases the log-odds, suggesting the area is likely empty.

Over time, genuine obstacles build a strong positive score, clearly marking them as occupied on our map. Meanwhile, random noise, causing occasional false readings, doesn't build enough consistent evidence, so these points eventually fade away, staying neutral or negative.

This approach makes the PolyMap occupancy grids more reliable and accurate, greatly improving how robots navigate and interact with their surroundings.

A Deeper Look into the Math of Log-Odds

Log-odds translate probabilities from their natural scale (0 to 1) into a 'logarithmic space,' which makes it easier and computationally more efficient to combine multiple pieces of evidence. In probability space, values close to 0 or 1 become increasingly difficult to update because small incremental changes can have disproportionate effects. By shifting to log-space, updating becomes straightforward additions and subtractions, maintaining precision across multiple sensor updates.

When updating log-odds:

  • Positive evidence: If our sensor indicates an obstacle, we add a fixed positive log-odds increment, quickly shifting the belief towards occupied.

  • Negative evidence: If the sensor suggests no obstacle, we subtract a smaller fixed increment, slowly shifting back towards uncertainty or free space.

To prevent numerical instability, clamps are applied to the log-odds values. These clamps set upper and lower bounds, ensuring values remain within practical limits, preventing overruns that could result in overly confident (extreme) occupancy or vacancy assertions.

This additive and controlled adjustment property allows for quick, stable, and efficient updating, even with multiple sensor readings, greatly enhancing computational efficiency and clarity in interpreting sensor data.

Practical Results from PolyMap

Since implementing log-odds in PolyMap, there are substantial improvements:

  • Reduction of False Negatives: Previously, true obstacles were sometimes overlooked due to sensor noise and rapid changes in sensor readings. By quickly reinforcing log-odds with positive detections and slowly decreasing with negative readings, our system now reliably maintains the presence of real obstacles, significantly reducing false negatives.

  • Clearer Obstacle Identification: True obstacles are now distinctly recognized after several confirmations, making navigation decisions safer and more confident.

These practical outcomes directly enhance robot efficiency, reducing navigation errors and improving path planning capabilities.

Future Improvements and Considerations

Moving forward, I will be exploring additional enhancements to further refine occupancy grid accuracy:

A key challenge is tackling the ultrasonic sensor's tendency to miss obstacles when pulses reflect at oblique angles, effectively making these obstacles invisible. This issue arises because the sensor’s sound pulse can deflect away, failing to return the echo needed for detection. To address this, I am considering several options:

  • Sensor Fusion: Integrating data from multiple sensor types, such as infrared, alongside our ultrasonic sensor to build an even more accurate picture.

  • Multi-Angle Sensors: Using multiple ultrasonic sensors at different angles to ensure obstacles are detected from various perspectives.

  • Adaptive Orientation: Dynamically adjusting the sensor's orientation or positioning to better capture reflections from challenging angles.

  • Enhanced Signal Processing: Implementing advanced signal analysis techniques to better distinguish weak echoes or indirect reflections.

I believe these improvements will further strengthen PolyMap's reliability, making the robot even smarter and more autonomous. 

Sunday, April 27, 2025

PolyMap & Minone: First Test Results (#4)

Simple SLAM Robot: Initial Tests with PolyMap and Minone

Both the PolyMap mapping platform and the Minone robot have reached a level of functionality sufficient for their initial tests together. The early results are exciting, though clearly revealing room for improvement—exactly as anticipated!

Here's a video capturing these very first tests. Having this visual documentation will greatly help us track the robot's progression as we refine and improve the SLAM capabilities.

Overview of the First SLAM Tests

These initial tests show how well the mapping system, PolyMap, works together with Minone, a very minimal robot platform that is built around an ESP32 microcontroller. Minone, equipped with only a single ultrasonic sensor, navigated a confined hallway environment. All communication between the robot and the mapping software was successfully managed via MQTT, demonstrating effective real-time integration.

Minone, alone in the hallway.


In this test setup, Minone was in a hallway out of my vision. My phone was recording its movement. I sat in an adjacent room behind the door and was instructing the robot through the visualization. Commands were sent using MQTT to the robot and telemetry and map data was returned as previously discussed. In this test, I did have the reassurance that it was working, as I could hear the motor movement down the hallway.

Mapping Process and Key Observations

The PolyMap visualization provided live feedback during the tests with telemetry and maps. On the map you can see:

  • Robot position was clearly indicated in orange.

  • Obstacles detected by the ultrasonic sensor appeared in red.

  • Unexplored areas remained marked in black.

  • Telemetry shows the Pose (X, Y, Θ) and state: Manual

PolyMap Viz April 2025 (Totally Not Evil Robot Army)

The visualization effectively demonstrated the system's ability to build a cohesive map from successive sensor readings. However, the tests quickly highlighted significant limitations associated with relying on a single ultrasonic sensor:

  • False negatives: The sensor occasionally failed to detect obstacles, particularly when encountering oblique angles. The pulsed ultrasonic signal reflects off the surface and does not return to the sensor, the device times out waiting, and returns a long distance (in this case greater than 170cm)

  • False positives: There were numerous instances of the sensor incorrectly registering obstacles due to noise and sensor inaccuracies. This could be due to echos, but otherwise indeterminate (for me at this time).

Minone, False Neg due to Reflection in corner.

These limitations underscore a common challenge when using ultrasonic distance sensors. In simple SLAM test, there were no special filter applied or sensor data management used. This is a clear direction for improvement in the next code iteration.

Initial Conclusions and Immediate Next Steps

The primary next step is addressing the significant number of false positive and negative readings produced by the ultrasonic sensor. To tackle this challenge, the mapping algorithm will transition from a basic binary representation (occupied/free) to a probabilistic occupancy grid, employing the log-odds methodology. This approach should significantly reduce the influence of sensor inaccuracies by statistically weighting sensor readings over time.

Looking Ahead: Introducing Mintwo and Exploring Swarms

Future development plans include building an upgraded version of Minone: the Mintwo robot (?!?). This iteration will be enhanced by incorporating multiple IR time-of-flight sensors, dramatically enriching the data quality and robustness. The improved sensory capability of Mintwo will not only enhance individual robot performance but also lay the foundational work for exploring coordinated behaviors and swarm robotics, leveraging PolyMap’s scalable and distributed architecture.

Stay tuned as we continue to iterate and enhance both the PolyMap platform and our expanding army, ehr.. family of robots!

Sunday, April 13, 2025

Minone – Getting the MV#Robot to Stable (#3)

Minone – Getting the MV#Robot to Stable (#3)

It's been a busy stretch since the last update! Many improvements, refactoring, debugging, and learning sessions have pushed the Minone MV#Robot toward a more stable and robust platform.

Code: Structure and States

Most of the recent effort has been dedicated to fleshing out software needed for remote operation. The robot's codebase has significantly expanded, particularly around the command structure. I've implemented a state model to handle essential high-level states:

  • Standby: Robot awaits commands—especially useful after a reboot, allowing manual verification before resuming tasks.

  • Manual: Direct control, crucial for immediate testing and remote operations.

  • Autonomous: Fully independent operation. Future enhancements will include advanced exploration strategies, frontier search, and swarm coordination.

  • Pause: Temporary halt; currently, the robot resumes directly into Autonomous mode.

  • Error: Safety state activated by unexpected issues.

High-level state changes are now managed via an MQTT subscriber, enabling remote state-level commands. In Manual mode, the MQTT listener also accepts individual task commands for immediate execution.

To efficiently handle robot actions ("tasks"), I developed wrapper code that allows manual triggering for debugging flexibility. Additionally, during Autonomous mode, the Robot's Agent autonomously generates tasks, utilizing the same task infrastructure.

Precise Movement Challenges

One aspect differentiating a true robot from a toy or simple remote-controlled device is the ability to move precisely. For mapping and SLAM purposes, it is crucial to know exactly where the robot is and its pose. To understand how much a motor has turned, an encoder is used to 'count' the amount of rotation. Minone uses encoders that are built into recycled Roomba wheel modules I am using. Knowing the number of pulses per rotation and wheel dimensions allows precise odometry calculations—determining how far the robot moves or rotates.

Initially, Minone exhibited incorrect odometry during turns. Calculations seemed accurate—asking to move 10cm resulted in software reports of 10cm—but the physical movement was actually 20cm. This discrepancy first appeared in rotation measurements, which were exactly half the physical result. At first, I assumed calculation errors were related to the complexity of the turn calcuation. The wheels are rotating in opposite directions and you must factor in wheelbase dimensions. A quick patch improved turn precision slightly, but the underlying movement issues remained.

Digging deeper revealed encoder pulse counts were half the expected values. Although the very specific 508.5 pulses per rotation was correct, I initially misunderstood that this value included both rising and falling edges of the encoder's square-wave pulse. A small adjustment resolved this completely:

// --- Encoder Setup ---
void setupEncoders() {
  pinMode(LEFT_ENCODER_PIN, INPUT_PULLUP);
  pinMode(RIGHT_ENCODER_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(LEFT_ENCODER_PIN), leftEncoderISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(RIGHT_ENCODER_PIN), rightEncoderISR, CHANGE);
}

Switching the interrupt trigger from 'RISE' to 'CHANGE' allowed counting both edges. Problem solved! Recognizing this resolved earlier incorrect adjustments, making robot turns and movements significantly more accurate—not perfect yet, but sufficient for this prototype stage.

A cautionary note on AI-assisted coding: Unfortunately, my AI coding companion missed this nuance, underscoring the continued need to have some knowledge of what you are working with, both in code and hardware. The AI repeatedly suggested using 'RISE,' which cost significant debugging time. Only through examining code from other experienced developers—a big shout out to Bill at DroneBot Workshop—did I discover the proper approach.

Coordinate Systems & Rotational Model

Choosing a coordinate system for your robot is critical, impacting navigation, mapping, and visualization. Coordinate systems aren't always the simple math we learned in high school (traditional X/Y axes). You must consider the Z-axis for rotation and eventually 3D mapping, plus how your robot’s "frame of reference" relates to mapping standards reference. Surprisingly, industry standards differ significantly from basic Cartesian assumptions.

I selected a right-handed system (X-forward, Y-left, Z-up) for the robot frame, aligning with robotics conventions used in platforms like ROS. Positive yaw indicates counterclockwise (left) rotation, while negative yaw indicates clockwise (right) rotation. Though initially counter-intuitive, maintaining consistency across code and system interfaces is very important.

Downstream, the Map Manager and visualization components must translate these standards, especially when interfacing with game engines like Godot, which often uses a different convention.

Minone 11 APR 2025 - MV#Robot

Hardware Improvements

The prototype hardware has also been improved in this iteration. Adding a level shifter stabilized communication between components with different voltage domains. To safely power the ESP32 independently, I now directly use a clean 5V source from a power bank rather than the L298N's regulator.

Currently, the robot remains on a breadboard— a big messy rats nest of jumper wires. Future builds will transition to a safer proto-board with robust connectors for stability and better cable management, reducing risk of havoc from loose connections.

Demos: Seeing the Progress!

Here are short videos captured during the build process. The first demonstrates basic movement and scanning routines without intelligent decision-making:

The second video shows progress after foundational movements were implemented but before odometry corrections—movements rely on timing rather than accurate angle calculations. Since filming, accuracy has significantly improved!


Thanks for following along! The Minone MV#Robot journey continues—iterate, iterate, iterate!

Saturday, March 29, 2025

Minone - Starting the build (#2)

Early minone - TNERA MVP Robot

Minone Build Log #2: The Grind Begins

Minone is a Minimal Viable Robot, built as a prototype for my development of PolyMap, a multi-agent mapping system. It’s a bare-bones junkyard robot—scrapped components, an ESP32-S, and some basic electronics. And let me tell you—it’s fantastic for learning how the real physical world and the virtual one collide. And boy, does it grind.

Core Components:

  • ESP32-S dev board
  • L298N - motor driver
  • Scraped Roomba Motor Modules (with encoders)
  • HC-SR04 Ultrasonic Sensor
  • MG90 servo
  • and a 12V/5V power bank

Here is a highlevel schematic of how they are connected together:


Everything’s wired up pretty simply—and that’s where the real problems start. 😄

Voltage in and out: 3.3V vs 5V

The ESP32 runs at 3.3V logic, but nearly all my sensors are 5V devices. That means input signals need to be stepped down, which I’ve handled using voltage dividers. No big deal.

The trickier part? Outputs. The PWM signals that drive the servo, trigger the ultrasonic sensor, and control the motor driver are all a bit underpowered at 3.3V. Some devices tolerate this. The L298N? Not so much.

That means it’s time to level shift those outputs. A voltage divider doesn’t cut it here. I’ll be testing a proper level shifter soon to ensure robust 5V signaling.

PWM Resource Conflicts on the ESP32

One of the most valuable lessons so far: PWM timers and channels are limited and easily conflicted.

At first, I had a nice ultrasonic sweep working via the servo—classic radar style. But after I added the motor drivers and encoder logic, the servo went haywire. High-pitched whining, jittering, and eventually… nothing.

What happened?

Turns out, the servo and motor driver were fighting over the same PWM channels. The servo took channel 0 by default. The motor driver was hardcoded to use channels 0 and 1. This conflicted both the frequency (servo at 50Hz, motor at 5kHz) and the timer allocations.

My first fix: reorder the initializations to let the servo grab a channel first. This helped, but wasn’t enough.

The real fix: manually assign a separate timer and channel to the servo. I dedicated timer 1 / channel 2 for the servo just before attaching its pin. That separated its timing cleanly from the motors—and everything started playing nicely again.

It cost me a bit of time, but the lesson was well worth it: don’t assume PWM resources will sort themselves out.

The Mysterious Unbootable EPS32

When shifting to local (robot power), Minone just… refused to boot. No errors, no logs—just silence. After way too much head-scratching, I finally pulled out the multimeter and discovered the culprit: the L298N motor driver was holding GPIO 12 and 15 high on startup. Turns out, those pins are a bit picky—they must be low for the ESP32 to boot.

No amount of code, pull-downs, or wishful thinking could override the voltage being held by the motor driver’s enable pins. The only fix? Move those signals to different GPIOs. I ended up switching to GPIO 19 and 22, and just like that—boot restored.

Sometimes, it’s not a bug—it’s physics.

A note about "Vibe Coding"

Throughout the development of Minone (and PolyMap), I’ve leaned heavily on LLM-based AI to guide the process—something the industry is starting to call Vibe Coding. As many have noted, Vibe Coding is a fast way to build software that might otherwise be just out of reach. And honestly? I recommend it, but not for beginners to coding.  [Update: Vibe Coding now has negative connotations. What I recommend is using it as a tool, not over dependence on it.]

AI has helped me rapidly prototype, implement features, and solve specific technical challenges. But beware—it’s not all smooth sailing.

Where AI shines is in writing clean, functional code based on known patterns. It’s excellent at pulling in details, explaining APIs, and even suggesting optimizations. But it often struggles with the human side of development—especially incremental development, integrating features, and aligning with the developer’s mental model.

A big part of my time is still spent going back through generated code line-by-line, trying to understand what it’s doing and whether it matches what I need it to do.

There are also assumptions—sometimes wrong ones. For example, in my code, the AI assumed the encoders were infrared-based, when in reality they’re Hall effect sensors. It’s up to the developer to catch those mismatches and feed corrections back into the conversation. Once pointed out, the AI adjusts quickly, but it doesn’t ask enough questions up front to avoid such errors.

Another example: determining the number of pulses per revolution from these old Roomba motors. This spec isn’t widely documented, and the AI couldn’t give a definitive answer. I had to dig through old boards and obscure web forums to figure it out.

The takeaway? AI is an incredible tool—but the human still needs vision, intuition, and a working understanding of how systems should behave. We’re closer than ever, but not quite at full autopilot.

Incremental Robot Development: ✅ Turning left 

Here’s a quick one: I connected one of the motors directly to the battery—pure chaos, purely intentional. 😄

Sure, it was just to confirm the motor worked. But from a test-first, incremental development perspective? We can officially check off:

✅ Turning left.

Progress!

State of the Platform

Minone is now stable. The essentials—WiFi, MQTT, sensor reads, sweeps, motor movement, and encoder feedback—are all working together smoothly.

The platform’s ready for the next steps:

  • Fully Integrated into the Godot visualization
  • A much much more curious Robot Agent
  • Mapping the basement hallway - the Testing Goal

Sunday, March 16, 2025

PolyMap: Leveraging Godot for Visualization (#3)

 


Leveraging Godot for PolyMap Visualization

Introduction to PolyMap and Visualization Goals
PolyMap is my experimental platform exploring multi-agent, robotic swarm-based Simultaneous Localization and Mapping (SLAM) strategies for mapping real-world environments. Development has been incremental: starting with a simulation, robot agents, a mapping manager, and basic visualization. One key goal was to leverage existing technologies like game engines to rapidly create an operable system. Game engines also offer a natural interface to visualize the spatial data generated by the robots as they explore physical space—and it’s just really cool!


Why Godot?

Why use a game engine instead of existing tools like RViz? There’s no single answer. RViz and others are developed for the ROS environment and are reportedly easy to integrate with ROS, but I haven’t explored ROS yet. Possibly due to hardware constraints or concerns about getting locked into a specific architecture, I’ve chosen instead to learn the fundamentals of the (robot) science before adopting fully developed solutions. This is also exactly why I’m starting with a single ultrasonic sensor rather than a Lidar for spatial sensing. There is a vast amount of unbuilt software needed in this space, and I want to understand the basics before being embedded in one ecosystem. (A related question: is ROS becoming a walled garden?)

I chose Godot because it’s open source, has no license fees, and has a large community. It can render immersive 3D environments with advanced graphics and interactivity—features that would take far longer to build from scratch. This makes enhancements like zooming, panning, robot tracking, and detailed exploration straightforward. Overall, it’s a friendly platform for any maker stepping into games or 3D visualization. Plus, Thor from Pirate Software thinks "Godot is the Blender of game engines". 😉


Transitioning from Python to Godot
The original Python-based visualization tool was a solid proof-of-concept for displaying the global map and robot telemetry. As the project matured, I planned to try a game engine to overcome Python’s limitations in interactivity and feature expansion—where everything had to be built from scratch. 

Transitioning to Godot 4.4 opened new possibilities for a more fluid UI. Because PolyMap uses MQTT for distributed communication, multiple visualization clients can consume the same data. This architecture also means each component can be developed in the best language for its function: the simulation and map manager will stay in Python, while the robot agent code (currently Python) will move to C++ for real hardware deployment. 🤖

Here is a video showing the transition and new version:



Integrating MQTT Communications in Godot
A critical aspect of PolyMap is using MQTT for data exchange. Fortunately, people like Goatchurch paved the way by porting MQTT code into Godot. It was showcased at a Godot convention, and most importantly, their open-source work on GitHub allowed me to clone it. Within a short time, messages from my existing Python version appeared in Godot. Once the MQTT connections and message handling were in place, communications were solid, letting me focus on features and functionality.

In the prototype, there’s a connectivity dialog box where the user enters the server URL, port, user ID, and password. It also allows topic subscriptions and message publishing. In this setup, the visualization subscribes to global_map and telemetry topics. Currently, the entire 100×100 integer map is broadcast every second, alongside telemetry data from the robot agents.


Using Godot
One of the most exciting aspects of Godot is how easily you can configure screens and add new features. Godot is heavily object-oriented; everything is a Node with attributes and methods. It reminds me of early “thick client” GUI development (pre-Web, like Acius 4D). This is my first Godot project, so I’m still learning and deciding how much to configure in the editor versus coding in scripts. Right now, screen elements are configured in the editor, while global map rendering is done in code.

There is a learning curve to working with Godot, but I understand it is very similar to other game engines like Unity and Unreal engines. For me, it was natural to code with an AI (or two, or three) to help me quickly learn how Godot works. There are also good videos on YouTube as well such as this video. One issue I ran into was that many of the AIs did not know how Godot 4.4 had changed from earlier versions, there was a constant effort required to correct the AI when it was off hallucinating. 🙂

Building the first prototype had its challenges: positioning the camera to view the map, deciding where to place MQTT functions, and balancing performance between rendering each grid point individually or using a grid mesh. Once I got the hang of Godot, it was surprisingly simple to get the visualization working. Adding a mouse-wheel zoom took only five minutes. I’m excited to add more capabilities quickly!


Future Features
With the first iteration working, here’s what I’m planning next:

  • Persistent Map: I can choose between redrawing the global map each time, or making it more persistent and only updating changed elements as the are discovered by the robot.
  • Free Fly - Camera: I will change the primary camera and give the user the ability to 'free fly' over the map moving around the landscape discovered by the robots. 
  • Robot FPV: It should be possible to put a camera in each of the virtual robots, allowing the user to select the robot and view the 3D space from its perspective.

Looking ahead, I’m breaking out the map manager and robot agent code from the simulation, moving toward a distributed computing platform. This is a key step for migrating from simulation to real hardware. Stay tuned for more updates!