Making Neato Robot ROS Package More Generally Usable

Neato mini USB cable connection to laptopNow that I have some idea of what happens inside ROS package neato_robot, and modified it to (mostly) run on my Neato vacuum, I thought I’d look in its Github repository’s list of issues to see if I understand what people have said about it. I didn’t have to look very far: top of its open issues list is “Robot compatibility.”

The author agrees this is a good idea but it has to be done by someone with access to other Neato units, pull requests are welcome. This comment was dated four years ago but my experience indicates no pull requests to improve compatibility were ever merged into this code.

But even though modifications were never merged back into this branch, they are still available. I just had to be pointed to know where to look. That comment thread referenced this particular commit which eliminates those problematic fixed arrays of expected response strings. Now it will read a Neato’s responses without trying to match it up against a preset list. Of course, if a specific response is still required (like LeftWheel_PositionInMM and RightWheel_PositionInMM) all it could do is ensure code will not hang, it is not capable of inferring requested data from other robot responses.

But possibly more interesting than code changes is this comment block:

This driver reads responses until it receives a control-z. Neato Robotics has
documented that all responses have a control-Z (^Z) at the end of the
response string:
CTRL_Z = chr(26)

That URL implies it was a link to some type of a programmer’s reference manual, but unfortunately that link is now dead. Still, this claim of control-Z termination (if true) gives me ideas on how I would approach talking to a Neato with my own code.

Neato Robot ROS Package Splits Laser Scan Read Operations

Neato mini USB cable connection to laptopI want to understand how the neato_robot ROS package works, and debugging its hang inside getMotors() was a great first step. Now that I’m past the problem of mismatching responses between author’s Neato robot vacuum and mine, I started looking at other parts of for potential explanations to its new (possibly timing-related) hang.

Since I had just examined getMotors(), I looked at its counterpart setMotors() and found nothing remarkable. getAnalogSensors(), getDigitalSensors(), and getCharger() used the exact pattern as getMotors() which meant they share the same fragility against different Neato responses but they’ll work for now. setBacklight() is nearly trivial.

That leaves the two methods for reading Neato’s laser distance scanner. Wait, two? Yes, and that’s where my concern lies. Other data retrieval methods like getMotors() would issue a command and then parse its response before returning to caller. For the laser distance scanner, this is split into a requestScan() which issues a getldsscan command and immediately returns. Reading and parsing laser distance data is done in a separate method getScanRanges() which the caller is expected to call later.

Why was this code structured in such a manner? My hypothesis is this was motivated by performance. When a getldsscan command is issued over serial, a lot of data is returned. There are 360 lines of data, one for each degree of laser scanning with distance and intensity information, plus a few more lines of overhead. This is far more than any of the other data retrieval methods. So rather than wait for all that data to transmitted before it could be processed, this two-call pattern allows the caller to go off and do something else. The transmitted data is trusted to be buffered and waiting in serial communication module.

But this only works when the caller is diligent about making sure these two calls always follow each other, with no chance for some other command to accidentally get in between. If they fail to do so, everything will fall out of whack. Would that cause the hang I’ve observed? I’m not sure yet, but it would be better if we didn’t even have to worry about such things.

Neato Robot ROS Package Expects Specific Response But Responses Actually Differ Between Neato Units

Neato mini USB cable connection to laptop

I got the outdated neato_robot ROS package mostly working just by adding a timeout to its serial communications. But this only masked the symptom of an unknown problem with no understanding of why it failed. To understand what happened, I removed the timeout and add the standard Python debugging library to see where it had hung.

import pdb; pdb.set_trace()

I found the hang was getMotors() in It is waiting for my Neato to return all the motor parameters specified in the list xv11_motor_info. This list appears to reflect data returned by author’s Neato robot vacuum, but my Neato returns a much shorter list with only a partial overlap. Hence getMotors() waits forever for data that will never come. This is a downside of writing ROS code without full information from hardware maker: We could write code that works on our own Neato, but we would have no idea how responses differ across different robot vacuums, or how to write code to accommodate those variations.

Turning attention back to this code,  self.state[] is supposed to be filled with responses to the kind of data listed in xv11_motor_info.  Once I added a timeout, though, getMotors() breaks out of its for loop with incomplete data in self.state[].  How would this missing information manifest? What behavior does it change for the robot?

Answer: it doesn’t affect behavior at all. At the end of getMotors()we see that it only really cared about two parameters: LeftWheel_PositionInMM and RightWheel_PositionInMM. Remainder parameters are actually ignored. Happily, the partial overlap between author’s Neato and my Neato does include these two critical parameters, and that’s why I was able to obtain /odom data running on my Neato after adding a timeout. (Side note: I have only looked to see there is data – I have not yet checked to see if /odom data reflects actual robot odometry.)

Next I need to see if there are other similar problems in this code. I changed xv11_motor_info list of parameters to match those returned by my Neato. Now getMotors() will work as originally intended and cycle through all the parameters returned by my Neato (even though it only needs two of them.) If this change to neato_robot package still hangs without a timeout, I know there are similar problems hiding elsewhere in this package. If my modification allow it to run without a timeout, I’ll know there aren’t any others I need to go hunt for.

Experiment result: success! There are no other hangs requiring a timeout to break out of their loop. This was encouraging, so I removed import pdb.

Unfortunately, that removal caused the code to hang again. Unlike the now-understood problem, adding a timeout does not restore functionality. Removal of debugger package isn’t supposed to affect behavior, but when it does, it usually implies a threading or related timing issue in the code. This one will be annoying as the hang only manifests without Python’s debugging library, which meant I’d have to track it down without debugger support.

Neato Robot ROS Package Runs After Adding Serial Communication Timeout

Neato mini USB cable connection to laptop

The great thing about ROS is that it is a popular and open platform for everyone to work with, resulting in a large ecosystem of modules that cover a majority of robot hardware. But being so open to everyone does have a few downsides, because not everyone has the same resources to develop and maintain their ROS modules. This means some modules fall out of date faster than others, or works with its author’s version of hardware but not another version.

Such turned out to be the case for the existing neato_robot ROS package, which was created years ago and while the author kept updated it through four ROS releases, that maintenance effort stopped and so this code is now five releases behind latest ROS. That in itself is not necessarily a problem – Open Source Robotics Foundation tries to keep ROS backwards compatible as much as they can – but it does mean potential for hidden gotchas.

When I followed instructions for installation and running, the first error message was about configuring the serial port which was fine. But after that… nothing. No information published to /odom or /base_scan, and no response to command sent to /cmd_vel.

Digging into the source code, the first thing to catch my attention was that the code opened a serial port for communication but did not set provision for timeout. Any problem in serial communication will cause it to hang, which is exactly what it is doing now. Adding a timeout is a quick and easy way to test if this is actually related.


self.port = serial.Serial(port,115200)


self.port = serial.Serial(port,115200,timeout=0.01)

And that was indeed helpful, restoring nominal functionality to the ROS node. I could now drive the robot vacuum by sending commands to /cmd_vel and I can see a stream of data published to /odom and /base_scan.

I briefly contemplated being lazy and stopping there, but I thought I should try to better understand the failure. Onward to debugging!

Existing Neato Robot ROS Package Will Need Some Updating

Neato mini USB cable connection to laptopNow that we’ve got a pretty good handle on getting old Neato robot vacuums up and running, including talking to their brains via USB, it’s time to dig into the software side and explore its potential. I could adapt my SGVHAK rover code to control a Neato, but I think it’s more interesting to get my Neato up and running on ROS.

Unsurprisingly, there already exists a neato_robot ROS package for Neato. Equally unsurprisingly, it was maintained up to ROS Hydro and since abandoned. ROS releases are named alphabetically, meaning this code is three releases behind my installation of ROS Kinetic and a whopping five releases behind the current state of the art ROS Melodic.

But hey, it’s all open source, if I want to get it to function all I need to do is roll up my sleeves and get to work. Since it’s out of date, I think it’d be a bad idea to grab the pre built package. Instead I followed links to its source code repository (last updated October 2014) and cloned it into my local catkin workspace.

And continuing on the trend of not being surprised, this code did not work immediately. Publishing events to /cmd_vel topic did not elicit robot chassis movement, and subscribing to /odom or /base_scan received no data. Fortunately this node was written in Python, which I hope would be easier to debug than a ROS node written in C++. But either way, it’s time to dig in and figure out what it is doing and how that differs from what it is supposed to be doing.

Trying To Charge Neato XV-21 Without a Charging Dock

When I found my Neato XV-21 in a thrift store, it wouldn’t power on and it didn’t have its charging dock. I originally thought I would buy it just for parts but it’s been a long and interesting adventure poking at how these robot vacuums worked. I made enough progress to catch [Emily]’s interest, who saw another Neato robot vacuum in a thrift store and bought it. Hers had the advantage of a charging dock so we could open one up to see how it worked.

Armed with that knowledge, I thought I’d try to build my own replacement charging dock for my Neato XV-21. I bought two 24V DC power supplies similar to that inside a Neato charging dock at the W6TRW swap meet. Since I didn’t have a good way to make contact with a Neato’s charging strips, I opened up my robot vacuum and rigged up a test configuration using clip leads on my crowded workbench.


The first candidate was a power supply with HP branding.

Neato charging experiments HP supplyThe HP power supply label said 24V @ 1.5A, which is slightly lower than 24V @ 1.67A listed on the supply inside a Neato dock, but I thought it was worth a try. First I verified the open circuit voltage was 24V, then I clipped it to the vacuum. The Neato woke up as if it saw a power source, but two seconds later it acted as if the power source went away. The output voltage on the power supply had dropped to under 1V and remained there. I thought maybe I burned out something, but if I unplug the power supply and plug it back in, its output open circuit voltage was back up to 24V. This behavior indicated no permanent damage was done, but that I had tripped some sort of internal protection mechanism.

I then did what, in hindsight, I should have done as my first step: tried charging my vacuum with a bench power supply. I set the dials to 24V and 1.67A, like the label on Neato charging dock’s power supply, and hooked up the wires. My bench power supply immediately hit the current limit I had set, and voltage has sagged to a little over 14V. Since that’s within the range of NiMH battery voltage, it appears the Neato had connected its DC input directly into its batteries.

NiMH batteries are fairly durable so this isn’t necessarily a horrible thing to do in and of itself. But it does mean the Neato will happily soak up more than the rated power of its power supply. Which explains the HP power supply behavior: it is an over-current protection mechanism that is great for safety in case of short circuits but inconvenient for trying to charge a Neato.

Neato charging experiments VeriFone supplyThe second salvaged power supply had VeriFone branding and claimed it is capable of 24V @ 1.7A. This is better than the unit inside a Neato, but now we know this is only part of the story. The real question is how it behaves when connected to something that draws more than its rated 1.7A. Does this also have an over-current protection mechanism?

The answer is yes. Again its open circuit voltage is 24V as advertised, but once wired to a Neato, the voltage collapses to around 12. After collapse, it quickly builds up to about 15, then the voltage collapses again. The Neato is very optimistic, showing itself to be charging, but there is negligible actual progress in charging the battery.

Neato charging experiments Dell supplyBut now that I know the current capacity is the limiting concern, maybe another power supply with higher amperage limits will work even if its maximum voltage is lower than 24 Volts. So I pulled out an old salvaged Dell power supply that only claims to deliver 19.5 V but could do so at 3.34 A. I verified its open circuit voltage was over 19 V, connected it to a Neato, which again woke up thinking there’s a charger. But once power started flowing, output voltage collapsed to 7.5V and remained there until a power cycle like the HP power supply did.

Out of three DC power adapters that should have worked on paper, zero actually worked for charging my Neato vacuum because the Neato drew far more than their rated amperage. So for the immediate future, my Neato charging will be done with my bench power supply. To make this easier, I made an easy (if not particularly pretty) modification to my Neato vacuum. Two holes were drilled on each side of a charging contact. Something I now have the confidence to do, because I’ve taken apart the charging contacts and saw there were no critical parts at risk.

Neato charging experiments drilled holes around contacts

With these holes drilled, it was easy to connect my bench power supply’s alligator clip leads for charging.

Neato charging experiments drilled holes around contacts for alligator clips

Trying To Make Two Good Neato XV Battery Packs From Four Bad Packs

Neato XV battery pack untouched

A Neato robot vacuum in their initial XV series product line is powered by a pair of identical 6-cell NiMH battery packs. When I picked up my XV-21 from a local thrift store it did not power up, a fault which I’ve isolated to its failed battery packs which I’ve since replaced to get the whole system back up and running with help of [Emily]’s loan of Neato charging dock. When I evaluated my battery replacement options earlier, one was to buy new cells and rebuild the pair of packs myself. I rejected that option because new cells actually would have cost more than pre-built replacement packs.

But since then Emily found a second thrift store Neato, a XV-12 with its own failing battery pack. This makes a total of four identical 6-cell NiMH battery packs. What are the chances we have sufficient good-enough NiMH cells in this pile for one set of healthy batteries? It costs nothing but a bit of time, well within the spirit of the kind of projects we tackle at SGVHAK meetups, and so it’s worth a shot.

First, the XV-12 battery packs were trickle charged overnight to get a sense of their capabilities, just like I did for XV-21 batteries earlier. Fortunately, the self-discharge profile looked promising.

Pack A: 7.93V self discharged to 7.64V after a few days.

Pack B: 6.59V self discharged to 5.98V after a few days.

Judging on voltage level alone, pack A is in better shape than pack B. The latter shows signs of having one completely dead cell. They’re certainly in far better shape than XV-21 battery pack. Out of 12 cells, only 1 held itself at ~1.3V after a week. The rest all self-discharged to a level ranging from 0.9V to flat zero after a few days.

So we disassembled pack B and deployed a volt meter to verify there is one cell that could only deliver around 0.1V. This cell was marked with an X and removed from the pack. Since we don’t have a battery spot welder available, we took care to make sure we keep the tabs on this pack.

The only not-dead cell from the XV-21 pack was marked with a check and removed from its pack. And again we took care to keep the battery tabs, this time making sure it stays with the ‘good’ cell.

Neato XV battery pack cell replacement

With the battery tabs intact, it was easy to solder a new pack together.

Neato XV battery pack soldering

A dab of hot glue helps the cobbled-together pack stay intact for installation into vacuum

Neato XV battery pack installed

When we turned on the robot vacuum, it no longer displayed a battery issue error screen, which is a great sign. We then left the robot sitting on its charger for about half an hour, then pressed the big red button to start a vacuum cycle. The vacuum suction motor turned on (Yay!) the brush motor turned on (Yay!) the robot started to move (Yay!) and then it went dark. (Noooo!) When we tried turning it back on, the error screen returned.

Neato XV battery pack still unhappy

Our little cell-swapping experiment did not result in a battery pack capable of running a Neato. It might find a future life powering low drain electronics projects, but it wasn’t enough to run a robot vacuum’s high drain motors. Emily ended up buying new battery packs as well to restore her XV-12 back to running condition.

Neato Robot Vacuum Is Certainly No Substitute For Manual Vacuuming

So now that we have the entire Neato robot vacuum system including charging dock, it’s time to see how did they perform at the job they were built to do. Not that their performance was important, as I bought my Neato intending to scavenge it for parts and it was only a happy accident to end up with a fully functioning robot vacuum. But before I start having fun with robot experiments, I should at least see how it works in its original capacity. Mostly just from curiosity, but if I’m going to show up at places with a modified robot vacuum, I also expect to be asked by people if it’s worthwhile to buy one to vacuum their home. Those who paid full price for their robot vacuums will have very different expectations from people like Emily and I who picked up our robot vacuums for cheap at a thrift store.

The common wisdom with Roomba robot vacuums is that they still miss a lot of surface area due to their random walk nature. A Neato, with its laser distance scanner, is supposed to provide full ground coverage. In reality, all open spaces are indeed very efficiently covered. However, the laser scanner meant a Neato is less effective at cleaning edges and corners. Because they’re smart enough to not run into edges and corners, a Neato’s cleaning brush never get close to those areas. We can see this most clearly in this picture of a dusty doorway after a Neato vacuuming pass, showing the smooth path it took swerving around a door frame instead of digging in and cleaning out those corners. (Note: my house is not quite as disgusting as the photo implies: the contrast has been exaggerated via Photoshop.)

Neato swerved around door frame high contrast

And naturally a little robot vacuum would not be able to move furniture out of the way. For example, floor under the dining table is not vacuumed, because the dining chairs legs all around the table blocked access. But it turned out the dining table itself was an obstacle. Just as my earlier experiment with Neato scanner had problem seeing certain furniture features, an actual Neato was unable to see the ramp-like shape of my dining table legs and managed to launch itself into an awkward angle and got stuck.

Neato stuck on dining table.jpg

As a home of a tinkerer, my house had many other features unfriendly to robot vacuums. My Neato kept getting itself tangled up in power cords for various AC-powered electronics gadgets, and of course there are piles of stuff all around the house in odd shapes, some of which share the “important features are out of sight of laser scanner” problem with office chair and dining table. There may be homes where a Neato would be a productive little housekeeping worker, but I’m afraid my home is just too much of a hazardous environment for this little Neato to be effective.

Which is great! I now feel less guilty about relieving it of vacuum duty and put it back to work for the reason I bought it: as a chassis for robotics projects. But it was fun to see a Neato in action doing its job. It was enlightening to see its own mapping and routing software at work, a benchmark to compare against for my own code driving this chassis. It is a really endearing little robot, with friendly messages on its LCD screen and my favorite part: the way it cuddles its charging dock. And now that one Neato is back up to full running condition, [Emily] and I will team up and try to get them both running.

Examining Neato XV-12 Charging Dock

When [Emily] found her Neato vacuum in a thrift store, it had an advantage over mine in that hers still have the company of its charging dock. This is our first look at a Neato robot vacuum charging dock and a chance to determine how one worked. We wanted to have some idea of what to expect when we put it to work charging newly installed replacement batteries.

Neato charging dock front

The charging dock is designed to sit against a wall. The two metal strips are obviously for supplying power, as they line up with the two metal wires at the back of a Neato vacuum. When the dock is plugged in, a volt meter reports 24V DC between those two plates, top plate positive and bottom plate ground. Each of the plate is mounted on a piece of spring-loaded plastic that allows approximately 3-5mm of horizontal movement. A Neato vacuum can press its wires against these plates to draw power.

Above the plates is a black plastic window, we expect something behind that window to communicate with the Neato so a hungry robot vacuum knows where to go to feed itself. How does it work? We hypothesized there are infrared emitters and receivers behind that panel, functioning like a consumer electronics remote control, to talk to a Neato vacuum.

Neato charging dock back.jpg

Neato charging power adapterThe orange tab on top looked very inviting as a way to open the dock. A bit of fiddling later, the dock was open. It was surprisingly simple inside. There was an AC power supply delivering 24V DC. It has a standard power cable on the input side, which can be routed to exit either side of the dock. This way a user can swap as needed to point towards the nearest power outlet, and possibly swap for a longer standard power cable if necessary to reach an outlet. The output wires of the power supply lead to the two metal plates, and that’s it.

Surprisingly, there’s nothing visible behind the black plastic window. The IR emitters and receivers we expected were absent, as were any circuit boards with components to communicate with the vacuum. So this charger dock location beacon must work passively. Now we’re really interested in finding out more. How does it work?

The black plastic window were held in place with a few clips. They stood between us and knowledge and were quickly dispatched. We were afraid the black plastic might be glued in place, but fortunately that was not the case and it popped off for us to see underneath.

Neato charging dock mystery panel demystified

We see a pattern laid out with two types of surfaces. The white segments are highly reflective much like the stripes on high visibility orange safety vests. The black segments are presumed to provide a contrast against the white parts. We found out earlier that a Neato lidar data stream returns both distance and intensity of reflections it saw. The distance is useful for navigation, but using just distance information the charger would be an unremarkable flat surface. This is where intensity comes into the picture: these surfaces behind the black plastic window will create a distinct pattern in reflection intensity, something a Neato robot vacuum can seek to find its charging dock.

Disassembling this passive system tells us two things:

  1. The engineers are Neato are quite clever
  2. We now know enough to try creating our own charging docks. Userful when we have Neato vacuums found at thrift stores without their charger.

Before we tackle new projects, though, let’s see how a full Neato system works in practice.

New Batteries for Thrift Store Neato Vacuums

The Neato XV-21 I found at a thrift store has bee joined by a XV-12 found by [Emily]. Both Neato robot vacuums found at thrift stores have degraded batteries. This was not a surprise as robot vacuums work their battery packs hard. Not just frequent charge and discharge cycles, but with a heavy power draw under use to run vacuum motors. This means both of these vacuums were retired by their previous owners when the battery no longer hold enough charge to perform its duties.

Neato XV-12 battery unhappy

Fortunately, this common failure also meant there’s a robust aftermarket for replacement batteries. Curiously, the economics of the markets are such that whole replacement packs can be ordered online for less than ordering individual cells and rebuilding the packs myself.

With previous experiments, I have gained confidence I can verify functionality of individual components using test mode accessible via USB port. And since this XV-12 was found with an official Neato charging dock, it’s time to install replacement batteries and test the full system.

The replacement batteries claim a capacity of 4000mAh which, on paper, is an increase from the original battery’s label capacity of 3800mAh. However, battery manufacturers play pretty loosely with these ratings so I expect the difference of 200mAh to be fairly insignificant in practice. When I took apart the original pack, I saw a thermister for monitoring temperature, an overcurrent protection fuse, and an overheat fuse. I assume the replacement pack has a thermister because the Neato computer can read it, but there’s no immediate way to tell if the overcurrent or overheat protection also exists on the new pack.

Neato XV-12 batteries old and new

With new battery packs installed, it’s time to put the robot up against the charging dock and verify the charging system works as expected. But before we do that, let’s take a closer look at this charging dock.

Cleaning Up a Thrift Store Neato XV-12

When I looked over my Neato XV-21, I thought it was far too clean to be a high mileage appliance. There were several possibilities:

  1. The previous owner was meticulous about keeping things clean (supported by the plastic protective wrap.)
  2. The Neato was barely used.
  3. A Neato is very good at keeping itself clean.

Thanks to a second Neato found in a thrift shop, this time a XV-12, we now know #3 is false. This one is well used, and very much looked it! The previous owner didn’t bother cleaning the vacuum before dropping it off at the thrift store. I have several loops of hair tangled up in the roller brush. The largest loop has even snagged what looked like furniture padding foam.

Neato XV-12 dirty brush roller

The largest loop has also tightened enough to damage the roller brush. Once I cut that loop of hair and debris off the roller brush, I see a slot cut into the rubbery blade.

Neato dirty brush roller ripped

There was also debris tangled on the motor shaft driving this roller that had to be cleaned off before the roller brush could function properly again. The cavity for the roller brush showed extensive wear from a life as a working household vacuum (above), whereas the XV-21 showed barely any wear (below).

Neato wear pattern

Those were problems found on the suction side of the vacuum. What about the exhaust side? The vacuum is generated by a fan which sat downstream from the dust bin filter, so its cleaniness would be a clue at how effective the filter was. Everything seen here are dirt particles that have made their way past the filter.

Neato XV-12 dirty vacuum motor

But even though this vacuum lived a harder life, its battery was actually in better condition and could run the vacuum computer for several minutes before fading out. As a result I was able to query all components individually using its USB port without having to hack a battery into this vacuum. All signs indicate that this robot vacuum is likely fully functional except for its battery.

And fortunately, with the arrival of replacement battery packs, we don’t need to drill holes and hack external battery packs for a full system test. We can install the new batteries into this XV-12.


Thrift Store Neato XV-12 Joins XV-21

I thought I was pretty lucky to find a Neato robot vacuum in a thrift store for $8. It didn’t work in the store and that’s why it was cheap, but I have since determined it was fully functional except for its battery pack. While waiting for its replacement battery pack to arrive, Player 2 has entered the game! [Emily] managed to find another Neato robot vacuum in a different thrift store. The new find is a model XV-12 and it included the charging dock for $11.

XV-12 And XV-21 A Pair of Neato

A little web research indicated that these two robot vacuums are contemporaries, both followed up Neato’s XV-11. The XV-12 is the direct successor that replaced the XV-11, and the XV-21 is a premium offering sold simultaneously with the XV-11. Aside from the cosmetic difference of purple plastic top on the XV-12, there are a few functional differences.

The cleaning brush roller is different. The XV-12 uses a bidirectional design with flat flexible plastic blades. The XV-21 uses a unidirectional design with a combination of flexible plastic blades and bristles. The XV-12 brush can be mounted in either direction – note that geared teeth to engage the toothed belt on both sides of the roller. The XV-21 is designed to spin a specific direction – brushing debris towards the center of the vacuum – and only has geared teeth on one end of the roller because it won’t work properly if mounted the other way.

Neato brush comparison

The dust bin filters are also different between these two models. While the XV-12 uses a flat sheet, the XV-21 uses a pleated design for greater surface area. This partially compensates for the more restrictive filter used in a XV-21 that captures more particles from the vacuum air stream.

Neato filter comparison

The XV-21 was sold as the upgrade model. Its roller brush with curved flexible blades and bristles combine to pick up more dirt, and its pleated filter keeps more of that dirt in the dust bin instead of passing it into the exhaust. These two differences reportedly improve vacuum capability at the cost of greater power consumption which translates to shorter run time on battery.

In addition to the design differences between the XV-12 and the XV-21, there are additional differences between these two specific thrift store finds. The XV-21 was surprisingly clean hinting at a very low usage in its previous life, but the XV-12 shows signs of a well-used robot vacuum.


Battery Replacement Options for Thrift Store Neato XV-21

With a successful all-up system test of Neato XV-21, running on hacked-up battery packs through a full vacuum cycle, we have confidence everything works on this thrift store bargain except for the battery pack. So focus returns to these NiMH battery packs.

Neato XV-21 battery pack

The default option is to buy some battery packs specifically built and sold as replacement batteries for a Neato robot vacuum. A straightforward Amazon search for “Neato battery pack” will return this “Amazon’s Choice” item (*) at $30 for 4000mAh NiMH packs.

A tempting choice is to purchase some lithium-ion batteries that would also fit in the Neato battery bay. However, battery packs sold on Amazon are typically designed for very high draw applications like multi rotor aircraft. Batteries designed to survive such use tend to have lower energy density. Thus lithium packs that fit within the bay’s dimensions actually don’t have much significantly higher capacity than 4000mAh.

Another concern with installing lithium batteries is the fact the Neato’s on board charging circuits are designed for NiMH battery cells, which has a different charging profile. Charging lithium cells as if they were NiMH cells risks damaging the lithium cells and possibly lighting them on fire.

There is, however, this item (*) which are lithium replacement packs intended and designed as an upgrade option for Neato vacuums. Not only are they sized appropriately to fit, they also have an integrated battery management system. Presumably, the circuit will make the Neato brain think there’s a NiMH battery pack installed but treat the lithium cells properly. At 4400mAh it offers 10% higher claimed capacity relative to default option, but at over double the cost, the cost/performance ratio is poor.

There is also the option to purchase new NiMH battery cells and rebuild the battery pack myself, reusing all the associated parts from the existing pack. I had expected this to be the lowest-cost option by supplying my own labor, but when I searched for NiMH battery cells in 4/3A form factor with 4000mAh capacity, I was surprised to find that they were selling for more than $3 per cell. There are 6 cells per pack and a Neato requires two packs, for 12 total cells. This means buying raw cells and rebuilding them myself would cost over $36, more than just buying a prebuilt pack from Amazon!

With this little bit of research into lithium upgrade option and rebuild option, it looks like the default $30 option is the best way to go. But before that replacement battery pack arrived, my Neato got a friend!

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.

Mounting External Batteries on Thrift Store Neato XV-21

Using small batteries I hacked into existing battery bays, I was able to query sensor status and command individual motors. However, those small batteries were not powerful enough to run multiple motors simultaneously, which meant a full system test would not be possible until larger batteries are installed. At this point I could order some Neato-specific replacement batteries and have decent confidence it will work, but I would like additional confirmation before I spend money.

But I didn’t have any batteries that were more powerful and still small enough to fit within Neato vacuum’s existing battery bays. This meant batteries had to be installed externally. And if they’re going to be external, I might as well go with the biggest batteries I have on hand: those I purchased for Sawppy the rover.

But where could I install those batteries?

Neato XV-21 with two big power packs

The most obvious place: is to put them on top of the dust collection bin. This is a popular location for Roomba battery retrofits, because it preserves weight balance and does not hinder Roomba operations. However, it won’t work for a Neato: this position blocks the line of sight for the laser distance scanner.

Neato XV-21 with two big power packs on top of dust bin

Installing the batteries on the front bumper would be out of lidar line of sight, and might help improve vacuum performance because their weight would help push the dirt brush roller into the ground. But this would change the behavior of front bumper which might confuse vacuum mapping algorithms, and using fragile batteries as your front bumper is never a good idea.

Neato XV-21 with two big power packs in front of bumper

If we put batteries off to the sides, it preserves front clearance but now it interferes with side clearance. The vacuum would no longer be able to follow along walls closely to vacuum near walls if the batteries are attached to the sides.

Neato XV-21 with two big power packs along sides.jpg

Installing behind the vacuum disturbs the weight balance – battery weight is now trying to lift the brush roller off the ground. The back is not flat, making installation difficult. And that circular shape is there for a reason – its clearance helps the robot turn in tight spots. Batteries install behind the robot would get bashed against obstacles as the robot turned.

Neato XV-21 with two big power packs behind

And obvious we don’t have any way to mount batteries on the underside of the vacuum, as there is only about 5mm of ground clearance. That exhausts all the potential directions we can go for external mounting.

Time to get creative.

Rethink top mounting, we recognize the problem is that we can’t block laser distance scanner’s line of sight. It looks out all around the top of the vacuum, except for one place: the raised protective cap above the lidar housing. Batteries mounted on top of that cap would not block laser scanner’s line of sight.

Neato XV-21 with two big power packs above lidar

Wires were run from battery compartment, which required drilling two small holes. To keep the power wire out of sight of lidar, the wires were routed through existing grooves on top of vacuum body and then followed existing  support pillars for lidar housing. This way, the wires should not introduce additional blind spots.

Neato XV-21 wire routing for two big power packs.jpg

With this hack, the robot vacuum can run on my pair of Sawppy rover batteries, which are plenty powerful enough to run all vacuum systems simultaneously. Now this little Neato is alive and can run through a full vacuum cycle, verifying that all systems worked.

Neato XV-21 up and running with two big power packs

Obviously, this won’t be the final long term solution. For one thing, Sawppy wants its batteries back. For another, the additional height on top of the robot hampers its ability to get under furniture for vacuuming, and the exposed wires are vulnerable to tangling on protrusions. What we need next are batteries powerful enough to run a Neato and fit within existing battery bays.

Sending Commands to Neato XV-21 Via USB

I’ve managed to establish a serial connection to my Neato XV-21 robot vacuum’s USB port, putting it into test mode and querying sensor status. The next step is to start issuing commands to see if individual components respond. We left off on querying control panel user interface buttons, so the next test is to see if I can draw my own user interface on that control panel screen. Typing in help setlcd retrieved the following information:

SetLCD - Sets the LCD to the specified display. (TestMode Only)
BGWhite - Fill LCD background with White
BGBlack - Fill LCD background with Black
HLine - Draw a horizontal line (in foreground color) at the following row.
VLine - Draw a vertical line (in foreground color) at the following column.
HBars - Draw alternating horizontal lines (FG,BG,FG,BG,...),
across the whole screen.
VBars - Draw alternating vertical lines (FG,BG,FG,BG,...),
across the whole screen.
FGWhite - Use White as Foreground (line) color
FGBlack - Use Black as Foreground (line) color
Contrast - Set the following value as the LCD Contrast value into NAND. 0..63

This is enough to draw simple test patterns, but it isn’t enough for me to put up legible text prompts for users. This was not a surprise as I understand this mode to be a diagnostics console and not intended to facilitate a completely new UI like what I wish to do. Still, I can probably convey some simple if cryptic information by drawing horizontal and vertical lines. The valid range is 0-127 inclusive. And while vertical lines across that entire range are all visible, horizontal lines beyond 124 seem to go under bottom lip of display bezel and not visible.

setlcd bgwhite
setlcd hline 20
setlcd vline 50
setlcd vline 100


I’ve already played with turning the laser distance scanner on and off. The next most important thing to get running on this chassis are the drive wheels. If I can’t make the robot move using this interface, nothing much else matters. Here are the commands listed under help setmotor:

SetMotor - Sets the specified motor to run in a direction at a requested speed. (TestMode Only)
LWheelDist - Distance in millimeters to drive Left wheel. (Pos = forward, neg = backward)
RWheelDist - Distance in millimeters to drive Right wheel. (Pos = forward, neg = backward)
Speed - Speed in millimeters/second. (Required only for wheel movements)
Accel - Acceleration in millimeters/second.
(Used only for wheel movements. Defaults to 'Speed'.)
RPM - Next argument is the RPM of the motor.
Not used for wheels, but applied to all other motors specified in the command line.
Brush - Brush motor forward (Mutually exclusive with wheels and vacuum.)
VacuumOn - Vacuum motor on (Mutually exclusive with VacuumOff)
VacuumOff - Vacuum motor off (Mutually exclusive with VacuumOn)
VacuumSpeed - Vacuum speed in percent (1-100).
RWheelDisable - Disable Right Wheel motor
LWheelDisable - Disable Left Wheel motor
BrushDisable - Disable Brush motor
RWheelEnable - Enable Right Wheel motor
LWheelEnable - Enable Left Wheel motor
BrushEnable - Enable Brush motor
SideBrushEnable - Enable Side Brush Motor motor
SideBrushDisable - Enable Side Brush Motor motor
SideBrushOn - Enable the Side Brush
SideBrushOff - Disable the Side Brush
SideBrushPower - Side Brush maximum power in milliwatts

The first few attempts – using just individual commands – were met with either errors or no movement.

setmotor lwheeldist 100
No recognizable parameters

The first successful command that triggered movement was one where I specified three parameters on a single line: left wheel distance, right wheel distance, and speed.

setmotor lwheeldist 200 rwheeldist 200 speed 100

The robot moves! This is great news, but we still have two more motors to play with. First up is the brush roller: I had expected the motor to be a digital on/off affair, or maybe something I can command with a particular motor power level. I was quite pleasantly surprised it can be commanded to a particular RPM because it implied a more sophisticated closed-loop feedback control system.

setmotor brush rpm 750
Run Brush Motor @ 750 RPM

The final motor control for vacuum suction fan was a little frustrating. I had no issue turning it on with setmotor vacuumon and then back off with setmotor vacuumoff. But I haven’t yet figured out the right syntax to command it to spin at a partial power level.

setmotor vacuumspeed
Value required for option VacuumSpeed
setmotor vacuumspeed 50
No recognizable parameters
setmotor vacuumspeed 50%

Invalid Command: 'setmotor vacuumspeed 50%'

I’ll update this post if I ever figure it out, but for now it is enough that it spins.

With this set of tests, I have determined that all individual components are functional, but only in a limited context. The limitation is because I could power just one device at a time with the small batteries I have installed. What I want next is a full system test. And for that, I will need to install more powerful batteries.

Query Neato XV-21 System Status Via USB

With the experimental batteries in place, the computer boots up and stays up for longer than three seconds. I tried to run a vacuum cycle but that was too much for these batteries to handle, so for now I’ll stick with digital exploration starting with the standard UI to dump revision information on components inside this particular Neato XV-21 robot vacuum.

Neato XV-21 info

After that, it’s time to go exploring through its USB serial port diagnostics!

I went straight to the most interesting component: the laser distance scanner module. First I had to put my vacuum into test mode with testmode on. Followed by setldsrotation on to turn on the motor that sweeps the laser. And finally, fetch a set of laser distance data with getldsscan. This gave me a large set of numbers. According to the legend given in the first line of the response, a distance and intensity number per degree of sweep. Partial excerpt below:


Future experimentation will determine what range of distance values are typical, similarly for intensity values. A quick web search failed to find a reference guide for error hex codes, but hopefully there’ll be a small set that I can infer from context. More exploration to come!

In the meantime, I repeated the getldsscan command to verify the values are getting updated. They were – I received a similar but slightly different set of numbers. Hooray! the most important sensor looks like it is functioning.

Next item on the exploration list: querying analog sensor values with getanalogsensors.


I see three distance sensors – wall and two drop sensors, one in each front corner. It’s not immediately clear what “Mag” in left and right “MagSensor” referred to. Do they detect magnetic fields? Or are they measuring magnitude of something? Bottom three lines indicate the vacuum sensor suite includes a three-axis accelerometer which can measure in units of thousands of G, implied by a Z value nearly 1000 in this vacuum sitting on the floor. Remainder of the values are related to power management.

The 3-axis accelerometer values above from getanalogsensors partially overlap with results of getaccel. As the name indicates, this one is completely focused on accelerometer readings and includes results of additional calculation in the form of pitch and roll in degrees and a sum of acceleration vector.

PitchInDegrees, -0.64
RollInDegrees, -0.53
ZInG, 1.079
SumInG, 1.079

The list of digital sensor values from getdigitalsensorsis shorter, and for whatever reason, labelled in all capital letters.

Digital Sensor Name, Value

Here we can see whether the dustbin is installed, whether either or both wheels are at full extension, and it looks like there are four switches associated with the front bumper. The top line shows if a charging plug is connected, but that’s only one of many parameters around power management. Most of the rest are in getcharger.


If I were to create my own control logic for driving my Neato around, the most important parameter here is FuelPercent. I’ll have to be responsible about not draining the battery too far, beyond that I can leave the rest in the capable hands of Neato’s existing battery management system. Speaking of power consumption, the biggest drains are the motors, and I can monitor them all with getmotors.

Charger_mAH, 0

Not very exciting at the moment, because all motors are at a stop. I imagine these numbers will get more interesting once the robot gets underway.

One final discovery in inputs: when in test mode, the user interface buttons on the top of the robot no longer trigger their usual functions. I could check for status of all five buttons via getbuttons which implies I could use these buttons in my own projects without worrying that I’ll trigger the default vacuum behavior. Cool!

Button Name,Pressed

But if I want to actually have my own user interface using those buttons, I would need to also display my own information on the LCD. Which brings us to phase 2 of playing over USB: start sending commands to see if components follow orders.

USB Serial Communication with Thrift Store Neato XV-21

My Neato XV-21 robot vacuum now boots up and stays running on a pair of old remote control helicopter batteries. This is a vast improvement over its comatose state when I found it in a thrift store. I pressed the start button to see if it’ll actually vacuum, but spinning up the motors drew too much current and aborted. Looks like these batteries are only good for probing electronics, which is still more than what I had before. And a good incremental step forward.

A few web searches on Neato technical details all pointed to posts on “Neato Robotics” forum on I guess this is where all the Neato robot tinkerers hang out. From this forum I learned of a tool for Neato maintenance that can help communicate with the vacuum as well as uploading firmware updates. Unfortunately, this forum also shared an update that Neato has taken these tools off their website. Without them, plugging the vacuum into my laptop running Windows would only result in a device without a driver.

On a lark, I rebooted my laptop into Ubuntu Linux and plugged in the vacuum. There were never any Neato drivers for this operating system, and I was curious what I could see via Linux tool dmesg.

[10547.714901] usb 1-1: new full-speed USB device number 25 using xhci_hcd
[10547.866228] usb 1-1: not running at top speed; connect to a high speed hub
[10547.876232] usb 1-1: New USB device found, idVendor=2108, idProduct=780b
[10547.876235] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[10547.876237] usb 1-1: Product: Neato Robotics USB v2
[10547.876239] usb 1-1: Manufacturer: Linux with fsl-usb2-udc
[10547.881256] cdc_acm 1-1:2.0: ttyACM1: USB ACM device

Well, that was more straightforward than I had expected. The ACM in ttyACM1 here stands for abstract control model. The operating system sees a communication device where all control is handled by the device, and all I had to do is treat it like a serial port. It’s not a true serial port, but close enough the technical differences aren’t important right now. What matters is the fact that I can run minicom --device /dev/ttyACM1 and issue a simple call for help. We are in business! The channel has been opened to talk to a Neato vacuum brain and see what it says in return.

Help Strlen = 1792
Help - Without any argument, this prints a list of all possible cmds.
With a command name, it prints the help for that particular command
Clean - Starts a cleaning by simulating press of start button.
DiagTest - Executes different test modes. Once set, press Start button to engage. (Test modes are mutually exclusive.)
GetAccel - Get the Accelerometer readings.
GetAnalogSensors - Get the A2D readings for the analog sensors.
GetButtons - Get the state of the UI Buttons.
GetCalInfo - Prints out the cal info from the System Control Block.
GetCharger - Get the diagnostic data for the charging system.
GetDigitalSensors - Get the state of the digital sensors.
GetErr - Get Error Message.
GetLDSScan - Get scan packet from LDS.
GetMotors - Get the diagnostic data for the motors.
GetSchedule - Get the Cleaning Schedule. (24 hour clock format)
GetTime - Get Current Scheduler Time.
GetVersion - Get the version information for the system software and hardware.
GetWarranty - Get the warranty validation codes.
PlaySound - Play the specified sound in the robot.
RestoreDefaults - Restore user settings to default.
SetFuelGauge - Set Fuel Gauge Level.
SetMotor - Sets the specified motor to run in a direction at a requested speed. (TestMode Only)
SetTime - Sets the current day, hour, and minute for the scheduler clock.
SetLED - Sets the specified LED to on,off,blink, or dim. (TestMode Only)
SetLCD - Sets the LCD to the specified display. (TestMode Only)
SetLDSRotation - Sets LDS rotation on or off. Can only be run in TestMode.
SetSchedule - Modify Cleaning Schedule.
SetSystemMode - Set the operation mode of the robot. (TestMode Only)
TestMode - Sets TestMode on or off. Some commands can only be run in TestMode.
Upload - Uploads new program to the robot.

Replacement Battery Test for Thrift Store Neato XV-21

My Neato XV-21 vacuum couldn’t power on when I found it in a thrift store, and the problem (or at least, the first problem) was its battery pack: it could only deliver enough power to boot up the onboard computer and run it for about three seconds before its voltage dropped and the computer shut down again.

In order to assess the rest of the vacuum, I need to find a replacement power source. Since the actual condition of vacuum components are still unknown, I’m not inclined to spend money buying batteries specific to a Neato robot vacuum. The next experiment, then, is to wire up something out of batteries already on hand. I looked through my pile of batteries to see which would satisfy the following criteria:

  • Delivers roughly 7.2 volt power of a six-cell NiMH battery.
  • Fits within Neato battery compartment.
  • A pair of identical batteries, one for each bay.

The winner was a pair of batteries I had for my E-Flite remote control helicopter. The aircraft itself had long since been crashed and trashed, but I kept its batteries. Two cell lithium polymer batteries have nominal voltage of 7.4 volts, within operational range of 6-cell NiMH batteries. Physically they were small enough to fit within the battery compartment, but at 800 mAh their power capacity is too low for full vacuum operation. They were designed to deliver high power to fly a small helicopter toy though only for a few minutes. Ideally they can run the vacuum motors briefly to verify their operation, but mostly I just need them to run the Neato computer for more than three seconds.

To connect these batteries, I first disassembled the old tired original battery pack. Before disassembly, I noticed a rectangular bump visible in the battery pack. I had thought this was the thermistor but I was wrong. It appears to be a temperature safety fuse. There was also an over-current protection amperage fuse in the pack. NiMH cells are relatively safe but it’s still good to see additional safety measures inside these packs.


I needed the battery pack’s wiring harness for my battery experiment, so it was cut free. I kept the temperature monitoring thermistor and wired the positive and ground battery power wires to a JST-RCY connector that will mate with my E-Flite battery pack.

E-flite Pack for Neato Vacuum.jpg

Once soldered, I connected the pair of batteries and plugged them into my Neato XV-21.

Neato XV-21 with adapted E-Flite LiPo batteries

I was greeted by the same boot-up screen I saw before, but this time the screen stayed on instead of flickering off after three seconds. Now it’s time to try talking to the vacuum.


Thrift Store Neato XV-21 Batteries Can’t Hold a Charge

The batteries from a Neato XV-21 were found to be flat and possibly damaged. This would certainly explain why the little robot vacuum failed to power on in the thrift store where I found it. Since the thrift store didn’t have its associated charging base, my fallback was to leave its batteries to trickle charge (at 100 milliamps) overnight on a bench power supply.

The battery packs nominal voltage was listed at 7.2 volt per pack, so the voltage limit for charging the pair in series was set to 14.4 volts. Fully charged six-cell NiMH battery packs can be up to 9 volts each, for 18 volts total, but setting to 14.4 volts leave us with a bit of headroom in case these batteries didn’t behave as expected. This turned out to be a good idea.

Neato battery pack on power supply

When I checked in on the batteries the next morning, I saw voltage sitting at 14.4 and amperage had dropped to 40 milliamps. If all goes well, the voltage would be evenly divided across the two packs at 7.2 volts each. This was not the case: one battery pack (I’ll arbitrarily name it pack A from now on) was holding at 7.9 volts and the other (now designated pack B) at 6.5. If it weren’t for the headroom, we might have inadvertently pushed pack A beyond its limits.

At this point I got distracted by other events and left the batteries sitting for a week. Checking in from time to time, I measured their open circuit voltage and wrote them down. It was an unplanned test of the batteries’ self-discharge rate.

Pack A:

  • Overnight trickle charge: 7.9 Volts
  • After one day: 4.67 V
  • After three days: 4.44 V
  • After a week: 2.0 V

Pack B:

  • Overnight trickle charge: 6.5 Volts
  • After one day: 2.77 V
  • After three days: 2.68 V
  • After a week: 2.61 V

While it is normal for NiMH batteries to self-discharge over time, this observed discharge curve was far more severe than is acceptable. Looking at the voltage discharge behavior for pack A, it appears to have two almost-useless cells, three weak cells, and one good cell. Pack B behavior hints at four almost-useless cells and two good cells.

But even if they are unusable to actually run the vacuum, they are still better than dead weight: these batteries can hold a charge for a very brief period. So they’re reconnected to the power supply to trickle charge again. I don’t expect them to actually power any motors, but perhaps there’d be enough to power on the electronics for a short while. Hopefully long enough for me to assess if the rest of the vacuum is healthy.

After another overnight charge, I plugged them into the vacuum and pushed the power button. Good news: they did power on the electronics for a short while. Bad news: It only lasted for three seconds, far too short to assess anything else about the vacuum. But the vacuum computer did boot, and that’s enough motivation to keep going.

Attempt to Charge Battery of Thrift Store Neato XV-21

We’ve pulled the battery packs out of a Neato XV-21 found in a thrift shop. We’re pretty sure the red and black wires are battery positive and ground, but if so we expect to see a few volts across those wires. The fact we measured less than a volt indicates this battery is flat and may even be damaged. Well, at least this would be consistent with the fact it wouldn’t power on in the thrift shop.

Fortunately, rechargeable NiMH battery cells are hardier than lithium batteries and they might possibly revive with a charge. And even if they were permanently damaged, they are less likely to turn themselves into fireworks as damaged lithium batteries sometimes do. Since we read a nonzero voltage, none of the cells have failed as an open circuit. Some might have failed as a short circuit acting as a resistor dissipating energy but not holding the charge. With this unknown, we shouldn’t charge the batteries anywhere near as fast as we could if they were known to be healthy.

Neato battery pack on power supply

A bench top power supply was set to a maximum voltage of 14.4 volts and maximum amperage of 0.1 amp (= 100 milliamps). Using a piece of wire, the two battery packs were connected in series. (Which is where the 14.4 came from: adding up their nominal 7.2 volts per pack.) Once connected in series, they were connected to the bench top power supply and we watched the meters.

If we instantly hit maximum voltage and maximum amperage, it indicates a short circuit somewhere, possibly in a dead battery cell acting as a resistor turning that 1.44 watts into heat. Fortunately this did not happen. We hit the 100 milliamp limit immediately as expected and the voltage started rising, which is consistent with a battery pack trying to charge.

After a few minutes, we disconnected the power supply and measured battery pack voltage: they now measure a few volts, higher than before. This increased confidence that our positive/negative wiring guesses were correct and also proof at least a few cells can hold some amount of charge.

The batteries were reconnected and, for the first half hour, we periodically touched each of the cells to see if they were hot to the touch. Some of the cells were warmer than others, but none were alarmingly so. We’ll let the power supply continue slowly feeding the battery pack overnight and see how they behave.