chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:04 +08:00
commit 43552b5423
52 changed files with 4226 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.private/
.claude/
.vscode/
+119
View File
@@ -0,0 +1,119 @@
# Bill of Materials (work in progress)
First working BoM is targeted for *mid-July '26*
A final, fully-costed BoM is targeted for *end of August '26*
Blog post - how I'm making this BOM [How-to: Source BOM for OOMWOO Open-Source Vacuum Robot](https://makerspet.com/blog/how-to-source-bom-for-oomwoo-open-source-vacuum-robot/)
Rationale in [docs/design-document.md](docs/design-document.md).
---
## Budget target
~$200 in sourced parts + a Raspberry Pi 5 (4 GB), aiming at the capability of a
mid-range ($500600) commercial vacuum with mopping, possibly premium obstacle detection (ToF +
color camera + an NPU accelerator).
## Robot BoM (work in progress)
Retail / low-qty prices, INCLUDES shipping, excludes tax. Read [how I calculate BoM costs](#how-i-calculate-costs).
| Item | Qty | ~USD | Notes | Source |
|---|---|---|---|---|
| Drive wheel assembly pair | 1 | $24-$33 | Motor + encoder + suspension + tire + cables + wheel-drop sensors | Roborock S4 Max, S45 Max, S5 Max, S50 Max, S55 Max, S6 MaxV, S6 Pure, S65 Pure, S65 MaxV, S7, S7 Pro, S7 MaxV, S7 Max Ultra, S70, S75, E4, E45, E5, E50, E55, G10, T7, T7S, Q5, Q7, Q7 Max, and Q Revo [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-maxv-wheels.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+maxv+wheels) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+maxv+wheels) |
| Drive wheel assembly pair | 1 | $24-$33 | | Roborock S5, S50, S51, S52, S55, S502, and the budget C10, E20, E25 and E35 [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-wheels.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+wheels) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+wheels) |
| Caster wheel | 1 | $2.50-$5 | Push-in | iRobot Roomba I3/4/6/8, J7/Plus J7 E5/6 500 600 700 800 900 [AliExpress](https://www.aliexpress.us/w/wholesale-roomba-caster.html) / [Amazon](https://www.amazon.com/s?k=roomba+caster) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roomba+caster) |
| Suction fan | 1 | $1023 | 6 kPa option | Dreame MSD-C-3, Nidec 20N709U020; fits Dreame L10s Prime/Pro, *L10s Ultra Gen 1* (5.3 kPa — Gen 2 is 10 kPa!), D10s Plus, X10+ (6 kPa); X20+ verify (~7 kPa?) [AliExpress](https://www.aliexpress.us/w/wholesale-Dreame-L10s-fan.html) / [Amazon](https://www.amazon.com/s?k=Dreame+L10s+fan) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Dreame+L10s+fan) |
| | | $1728 | 5.1-6 kPa option | Nidec 22N704W150, 20N704S980, 20N704R980L; fits Roborock S8 Pro Ultra, S7 MaxV (5.1 kPa), S8/S8+/S8 Plus (6 kPa); G20 (Q7 Max/Max+ excluded — they're 4.2 kPa, not 5-6) [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s8-pro-fan.html) / [Amazon](https://www.amazon.com/s?k=roborock+s8+pro+fan) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s8+pro+fan) |
| | | $1224 | 10 kPa option | Roborock BL24131616; Nidec 22N704V160; fits Roborock S8 MaxV Ultra (10 kPa); G20S [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-g20s-fan.html) / [Amazon](https://www.amazon.com/s?k=roborock+g20s+fan) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+g20s+fan) |
| | | $3445 | 36 kPa option | Roborock Saros 20 [AliExpress](https://www.aliexpress.us/w/wholesale-Roborock-Saros-20-fan.html) / [Amazon](https://www.amazon.com/s?k=Roborock+Saros+20+fan) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Roborock+Saros+20+fan) |
| | | $1225 | 2-2.5 kPa option | Nidec 20N704P200, 20N704R500, 20N704R310, 20N704P160; Xiaomi Roborock S50 S51 S55 S60 S61 S65 S5 MAX S6 E25 E35, S5, S6 Pure [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-fan.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+fan) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+fan) |
| Main brush | 1 | $5-$8 | Single roller, rubber and bristles | Fits Roborock S4, S4 Max, S5, S5 Max, S50/S55, S6, S6 Pure/MaxV, S60/S65, E2,E3,E4,E5, E20,E25,E35, C10, Xiaomi Mijia [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-brush.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+brush) |
| | | $8-$12 | Anti-tangle dual roller, rubber only | Fits Roborock S8 MaxV Ultra, G20S V20, P10S Pro/Pro Plus [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-G20S-v20-brush.html) / [Amazon](https://www.amazon.com/s?k=roborock+G20S+v20+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+G20S+v20+brush) |
| | | $3-$5 | Single roller, rubber only | Fits Roborock S7, S70, S7+/MaxV/MaxV Plus/MaxV Ultra/Pro, Q Revo/Pro/Plus, Q5, Q5+, Q7, Q7 Max, QV 35S, T7S, T7S Plus [AliExpress](https://www.aliexpress.us/w/wholesale-Roborock-S7-main-brush.html) / [Amazon](https://www.amazon.com/s?k=Roborock+S7+main+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Roborock+S7+main+brush) |
| | | $5-$10 | Anti-tangle split single roller, rubber and bristles | Fits Roborock Saros 10/10R, Saros 20, Saros 20 Sonic, S10 MaxV Ultra, S9 MaxV/MaxV Ultra, QRevo 5AE/Edge/Edge C/Edge S5A/5V1/X/Edge T/Curv/S5V/Curv S5X/P20 Pro [AliExpress](https://www.aliexpress.us/w/wholesale-Roborock-saros-10-main-brush.html) / [Amazon](https://www.amazon.com/s?k=Roborock+saros+10+main+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Roborock+saros+10+main+brush) |
| | | $20-$30 | Anti-tangle hair-cutting dual roller, rubber and bristles | Fits Dreame L40s Ultra, L40 Ultra, X40, X40 Ultra, X30 Ultra, L30 Ultra, L20 Ultra, L10s Ultra/Ultra Gen 2/Pro Ultra, S10 Pro, S20, S20 Pro/Pro Plus, X10, X10+, X20 Pro, D9 Max Gen2; Mova E30 Ultra, S10 Plus, P10 Pro Ultra; Xiaomi S10+, X20 Pro, X10+, Mijia M30S/M40 [AliExpress](https://www.aliexpress.us/w/wholesale-dreame-tricut-brush.html) / [Amazon](https://www.amazon.com/s?k=dreame+tricut+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=dreame+tricut+brush) |
| | | $7-$10 | Anti-tangle tapered dual roller, rubber and bristles | Fits Dreame X60 Max, X50 Ultra/Master/Pro Ultra, L40S Pro Ultra/Ultra; MOVA V50 Ultra [AliExpress](https://www.aliexpress.us/w/wholesale-dreame-x50-ultra-main-brush.html) / [Amazon](https://www.amazon.com/s?k=dreame+x50+ultra+main+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=dreame+x50+ultra+main+brush) |
| | | $7-$10 | Anti-tangle dual roller, rubber only + rubber and bristles options| Fits Roborock Roborock S8, S8+/Pro Ultra, Q5 Pro/Pro+, Q8 Max/Max+, G20 [AliExpress](https://www.aliexpress.us/w/wholesale-Roborock-Q8-Max-main-brush.html) / [Amazon](https://www.amazon.com/s?k=Roborock+Q8+Max+main+brush) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Roborock+Q8+Max+main+brush) |
| Battery pack + BMS | 1 | $1630 | OEM BRR-2P4S-5200, 4S2P-MMBK P2150-4S2P-XWDLS; 14.4V Li-ion, ~5200 mAh / 75 Wh in-pack BMS. | Fits Xiaomi Mijia 1/1S/1C/1T/G1, Roborock S4/S5/S5 Max/S6/S6 Pure/MaxV/S7/S7 MaxV, Q5/Q7 Max, T4/T6/T7/T60/T65, Xiaowa E20/E25/E35, C10 [AliExpress](https://www.aliexpress.us/w/wholesale-BRR-2P4S-5200-battery.html) / [Amazon](https://www.amazon.com/s?k=BRR+2P4S+5200+battery) / [eBay](https://www.ebay.com/sch/i.html?_nkw=BRR+2P4S+5200+battery) 4-pin connector B+, B, NTC, sense (TODO verify). Safety review narrows to: 16.8 V CC/CV charge + NTC temp sense. |
| 2D LiDAR | 1 | $16-26 | PCB mark X-WPFTB-V2.6.2, possibly Camsense | Fits Dreame L10s/Pro/Ultra/Prime, L10 Ultra, L20 Pro/Ultra, L30 Ultra, W10s/Pro, W20, Xiaomi X10+, X30s Pro, X30 Plus/Pro/Ultra, X20 Pro, X40/Pro/Pro Plus, X10/Plus, X20/Plus, S10/Pro/Plus/Ultra, S20+ |
| | | $16-27 | PCB mark X-Wireless board-V1.13.3, possibly Camsense | Fits Dreame W10, F9, D9, D9 Pro/Plus/max, L10 Pro, Z10 Pro [AliExpress](https://www.aliexpress.us/w/wholesale-Dreame-L10s-lds.html) / [Amazon](https://www.amazon.com/s?k=Dreame+L10s+lds) / [eBay](https://www.ebay.com/sch/i.html?_nkw=Dreame+L10s+lds) |
| | | $14-32 | Xiaomi LDS02RR, LDS01RR | Fits Roborock S5/S50/51/55/S6/S7, S502-00/01/02/03, S550-00, S5 Max, S6 MaxV/Pure, S45 Max, S7 Max, Xiaomi Mi 1s [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-lds.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+lds) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+lds) |
| | | $13-25 | 3irobotix Delta-2A/B/G | Fits Xiaomi Mijia Mop P Pro, Mop 2S, 3C S10, S12 T12 [AliExpress](https://www.aliexpress.us/w/wholesale-xiaomi-mop-lds.html) / [Amazon](https://www.amazon.com/s?k=xiaomi+mop+lds) / [eBay](https://www.ebay.com/sch/i.html?_nkw=xiaomi+mop+lds) |
| | | $13-23 | Possibly LDROBOT LD14P | Fits Dreame X30 X40 Series, Xiaomi Mijia X20 Plus, Mop2 [AliExpress](https://www.aliexpress.us/w/wholesale-dreame-x30-lds.html) / [Amazon](https://www.amazon.com/s?k=dreame+x30+lds) / [eBay](https://www.ebay.com/sch/i.html?_nkw=dreame+x30+lds) |
| Compute Module | 1 | $95 | Raspberry Pi CM4 ≥4 GB | CM4104000 [PiShop](https://www.pishop.us/product/raspberry-pi-compute-module-4-wireless-4gb-lite-cm4104000/) [Newark](https://www.newark.com/raspberry-pi/cm4104000/rpi-compute-module-4-lite-4gb/dp/86AH2101) [DigiKey](https://www.digikey.com/en/products/detail/raspberry-pi/SC0671/13530944) CM4104008 [Newark](https://www.newark.com/raspberry-pi/cm4104008/rpi-module-4-4gb-ram-8gb-emmc/dp/86AH2103) CM4104016 CM4104032 |
| | | $72.50 | Raspberry Pi CM5 ≥4 GB | CM5102000 [PiShop](https://www.pishop.us/product/raspberry-pi-compute-module-5-wireless-4gb-ram-lite-cm5104000/) [Newark](https://www.newark.com/raspberry-pi/cm5104000/som-rpi-compute-mod-5-lite-2gb/dp/20AM3783) [DigiKey](https://www.digikey.com/en/products/detail/raspberry-pi/SC1592/25805568) CM5102016 CM5102032 CM5104064 |
| | | $62.50* | Raspberry Pi CM4 ≥2 GB | *if software fits CM4102000 [PiShop](https://www.pishop.us/product/raspberry-pi-compute-module-4-wireless-2gb-lite-cm4102000/) [Newark](https://www.digikey.com/en/products/detail/raspberry-pi/SC0667/13530921) [DigiKey](https://www.newark.com/raspberry-pi/cm4102000/rpi-compute-module-4-lite-2gb/dp/86AH2093) CM4102008 CM4102016 CM4102032 |
| | | $72.50* | Raspberry Pi CM5 ≥2 GB | *if software fits CM5102000 [PiShop](https://www.pishop.us/product/raspberry-pi-compute-module-5-wireless-2gb-ram-lite-cm5102000/) [Newark](https://www.newark.com/raspberry-pi/cm5102000/som-rpi-compute-mod-5-lite-2gb/dp/20AM3775) [DigiKey](https://www.digikey.com/en/products/detail/raspberry-pi/SC1586/25805584) CM5102016 CM5102032 CM5104064 |
| Cliff sensors | 4 | $1.50-2.50 ea | 4x cliff + 2x bumper w/cables bundle | iRobot Roomba 500 600 700 800 528 552 564 595 560 570 610 615 620 625 630 650 [AliExpress](https://www.aliexpress.us/w/wholesale-irobot-roomba-500-cliff.html) / [Amazon](https://www.amazon.com/s?k=irobot+roomba+500+cliff) / [eBay](https://www.ebay.com/sch/i.html?_nkw=irobot+roomba+500+cliff) |
| Bumper switches | 2 | $0 | Included in cliff sensors bundle | |
| Main brush motor, gearbox | 1 | $7-11 | Requires brush socket adapter | Fits Roborock S5, S50, S51, S52, 55 502-00/01/02/03**S552-00, S6; Xiaowa C10, E20, E25, E35 [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-main-brush-motor.html) / [Amazon](https://www.amazon.com/s?k=roborock+s5+main+brush+motor) / [eBay](https://www.ebay.com/sch/i.html?_nkw=roborock+s5+main+brush+motor) |
| Side brush + motor | 12 | 38 | fixed (extendable is later) | |
| Mop spin motor(s) + pads | 12 | 615 | mopping models only | |
| Water pump + valve + tubing | 1 | 410 | mopping models only | |
| Mop lift servo | 1 | 26 | mopping models only | |
| VL53L7CX multizone ToF | 1 | 815 | obstacle detection (90° FoV) | |
| Color camera | 1 | 515 | connects to the SBC | |
| Side proximity sensors | 34 | 38 | | |
| Ultrasonic carpet sensor | 1 | 25 | | |
| Speaker + amp, mic, LEDs, buttons | — | 38 | | |
| Custom I/O PCB | 1 | 2040 | STM32 + motor drivers + sensor front-ends | |
| Wiring, connectors, fasteners, magnets, gaskets, filter | — | 1225 | | |
| Printed parts (filament) | — | 515 | you print these yourself | |
| *Robot subtotal (sourced parts)* | | *~$130270* | excludes SBC | |
> *Fan sourcing caveat:* the *kPa is the fan's own rating* — verify it against the fan's
> model number / datasheet. The vacuum models are a *sourcing search aid only*: a fan listed
> as "fits vacuum X" is *not* necessarily X's original fan (lower-power replacements are sold as
> compatible for higher-suction models). Omit any model whose known suction contradicts the row.
## Dock (by tier)
Three dock tiers share one robot base, released in order:
| Tier | Adds | Rough extra parts |
|---|---|---|
| Basic charge (first release) | charging only | printed housing + contacts/magnets + wall adapter + IR beacon |
| Auto-empty | dust auto-emptying | dock fan + bin/bag + sealed port |
| Auto-empty + wash + dry | mop wash + hot-air dry | clean + dirty tanks, 2 pumps, heater + fan, own ESP32 + WiFi controller |
## Sourcing strategy
- Print geometry, source mechanisms and wear items. See the print-vs-source table in
[docs/design-document.md](docs/design-document.md#2-print-vs-source-strategy).
- Spec wear parts (brushes, filters, wheel modules) in *common, abundant sizes* so
builders can buy cheap universal replacements anywhere.
- Per-module sourcing details will land in the relevant
[contributions/](contributions) RFCs as they mature.
## How I calculate BoM costs
Prices for vacuum cleaner aftermarket parts have a wide spread (3x max/min as a ballpark).
You can buy same part for, say, $15 or $30 or even $45.
Therefore, if you search [AliExpress](https://www.aliexpress.us/w/wholesale-roborock-s5-lidar.html)/eBay/Amazon
for, say, "Roborock S5 LiDAR", you will likely see offers around $30.
There will probably several pages worth of those offers. And if you dig through those pages, you will probably start
finding parts for $25.
If you are shopping on AliExpress, open a part listing and check for free coupon offers like "$2.00 off on $18.00".
That brings your $25 price down to $23. Also, check the fine print in red font saying something like "$21.43 each, ≥ 3 pieces".
That's not the end of it. The next AliExpress trick is to wait for seasonal sales and promotions. Those happen relatively often.
Sales and promotions can bring prices down even futher.
How can you get to the rock-bottom $15 price? Aftermarket parts prices depend on your search keywords.
Try searching for multiple keyword variations: "Roborock S5 LDS", "Roborock S5 LiDAR", "original laser distance lds",
"LDS02RR", "LDS02RR LiDAR", "Roborock LDS02RR", "LDS laser sensor for xiaomi Roborock" and so on. Be creative and persistent.
Try searching Google "site:aliexpress.com lds02rr" and other variations. Be creative and persistent.
Combining all these methods is how you can get the rock bottom $15 price.
I record two prices for each part in the BoM table, for example "$15-$25".
The first ($15) price is the rock bottom one that often requires a minimum purchase of 3 pieces - what I'd (or anyone) be paying putting together an OOMWOO all-parts-included convenience kit.
The second ($25) price is what you can realistically get when purchasing 1 piece, *applying coupons* and doing *a few minutes of search*.
Why record the first price if requires a 3 pcs minimum? This is because I intend to assemble a convenient parts kit - and make it available to everyone.
+31
View File
@@ -0,0 +1,31 @@
# Build Instructions
> *Not available yet — this is a placeholder.*
>
> OOMWOO is currently at the *design / RFC stage*, not the build-it-yourself stage.
> Step-by-step build instructions will arrive once the first
> [Bill of Materials](BOM.md) and parts are validated (*first BoM targeted ~mid-July*)
> and the modules are proven on real hardware.
## Where the project is right now
- *Design + interfaces:* [ARCHITECTURE.md](docs/ARCHITECTURE.md)
- *Parts list (draft):* [BoM.md](BOM.md)
- *Modules being built (RFCs):* [README → Requests for Contributions](README.md#requests-for-contributions)
- *Design decisions + research:* [docs/design-document.md](docs/design-document.md)
## What you can do now
- *Star / watch* the repo to follow along — that's the best way to know when build
instructions land.
- *Contribute:* pick a module from the [RFC list](README.md#requests-for-contributions)
and dive in — every skill level welcome.
- *Join the community:* [Discord](https://discord.gg/3y2JKz5T25) and
[GitHub Discussions](https://github.com/makerspet/oomwoo/discussions).
- *Follow the build in public:* [YouTube](https://www.youtube.com/@makerspet).
## What this page will become
A complete, zero-to-hero guide: sourcing the parts, printing the chassis, assembling the
robot, flashing the firmware, bringing up ROS2, and first clean — with photos and videos.
We'll make it a clear, standalone guide the moment there's something to build.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+176
View File
@@ -0,0 +1,176 @@
<div align="center">
# OOMWOO
*Open-source robot vacuum you build yourself.*
Clean well · Hackable · Raspberry Pi · ROS2 · Home Assistant · 2D LiDAR · 3D printed · ESP32 · Arduino
![License](https://img.shields.io/badge/license-Apache--2.0-blue)
![Status](https://img.shields.io/badge/status-early%20development-orange)
</div>
## What is this?
OOMWOO is an *open-source home robot vacuum* you can build yourself, made for the
Raspberry Pi, ROS2, Home Assistant, and 3D-printing communities. It uses an
affordable 2D LiDAR to map your home and navigate on its own. Local, no
cloud required for regular functionality, no vendor lock-in. Follow us building in public
[Discord](https://discord.gg/3y2JKz5T25) | [X](https://x.com/@0OMWO0) | [Instagram](https://www.instagram.com/oomw0o/) | [Facebook](https://www.facebook.com/profile.php?id=61591466775035) | [Reddit](https://www.reddit.com/r/oomwoo/) | [newsletter](https://stats.sender.net/forms/bo2rAK/view) | [YouTube](https://www.youtube.com/@makerspet) | [oomwoo.com](oomwoo.com) | [Tutorials](https://makerspet.com/learn/)
Reference design images - this is approximately how the finished design will look:
![Reference robot vacuum cleaner top](./assets/vacuum_model_top.webp)
![Reference robot vacuum cleaner bottom](./assets/vacuum_model_bottom.webp)
![Reference robot vacuum cleaner - top cover removed](https://github.com/makerspet/oomwoo/blob/main/assets/vacuum-no-top-back.webp)
## Goals
- Affordable, fully open hardware, software and firmware
- Home appliance product quality - not a throwaway build
- Easy to build, with step-by-step zero-to-hero instructions
- 2D LiDAR mapping and autonomous navigation (ROS2 / Nav2)
- Native Home Assistant integration for local control
- 3D-printable, documented, and hackable chassis
- Buildable from parts you source yourself
- Local, no cloud required for regular functionality
- Optional extra functionality when connected cloud
- Apps on top of ROS2 to customize vacuum operation
- Stretch goal: App store
- Stretch goal: LeRobot integration, OpenClaw
*v0 target: bare-bones build:*
- 3D-printed chassis
- ROS2 Gazebo sim
- LiDAR with manual SLAM
- ROS2 on Raspberry Pi 5 AND/OR ESP32 running micro-ROS with ROS2 on local PC - decision TBD
Open Source Deliverables:
- [x] [Software development environment](https://github.com/makerspet/oomwoo-install), robot [description package](https://github.com/makerspet/oomwoo-one/) and [tutorials](https://makerspet.com/blog/simulate-oomwoo-one-robot-vacuum-in-gazebo-with-ros-2/) (ROS2)
- [x] Placeholder real [vacuum cleaner](https://github.com/makerspet/proscenic-m6pro) and [tutorials](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/) (temporary while OOMWOO is being designed)
- [ ] [Bill of materials (BoM)](BOM.md) (in progress)
- [ ] 3D-printable files
- [ ] Firmware
- [ ] Motor drivers and sensors [I/O PCB](https://github.com/makerspet/oomwoo-io-board)
- [ ] Build, setup, bringup and troubleshooting [instructions](BUILD_INSTRUCTIONS.md)
- [ ] Demo video(s)
## Contributing
Would you like to contribute? See [CONTRIBUTING](docs/CONTRIBUTING.md) for the full guide.
OOMWOO is organized to built by the community, massively *in parallel*.
The vacuum and its software are subdivided into [modules](#requests-for-contributions), see list below.
A volunteer picks whatever module she wants and works on it whenever she wants.
For *code and simulation* modules she builds her package in her *own repo* and sends
a short PR *linking* it from the module; for *docs and specs* she contributes files
in-tree under `contributions/module-name/<her-github-username>`. See
[CONTRIBUTING](docs/CONTRIBUTING.md) for how this works.
Multiple developers are welcome to work on the same module.
The best solution for each module surfaces over time, with the project master having the last call.
1. Pick a contribution from the [list below](#requests-for-contributions).
2. [Let us know](https://github.com/makerspet/oomwoo/discussions) you're working on it and your progress.
3. Check [ARCHITECTURE.md](docs/ARCHITECTURE.md) and
[SOFTWARE_INTERFACES.md](docs/SOFTWARE_INTERFACES.md) for the system design
and ROS2 interfaces.
## Requests for Contributions
Every module below is *actionable now* — build it against the Gazebo simulation
([oomwoo-one](https://github.com/makerspet/oomwoo-one)) or a real *placeholder robot*
(a [Proscenic M6 Pro connected to ROS2](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/)),
until OOMWOO hardware is ready. Pick one, tell us in
[Discussions](https://github.com/makerspet/oomwoo/discussions), build it in your own
repo (docs and specs go in-tree), and send a short PR linking it from the module.
| Module | ID | Status | Notes |
|---|---|---|---|
| ROS2 URDF + Gazebo sim | [urdf-gazebo-sim](./contributions/urdf-gazebo-sim) | In progress | Placeholder URDF + Gazebo sim (reference: [oomwoo-one](https://github.com/makerspet/oomwoo-one); [@alvarosamudio](https://github.com/alvarosamudio/oomwoo_gazebo) featured), refined when hardware lands |
| First clean: coverage + mapping + exploration | [clean-and-map](./contributions/clean-and-map) | Ready to start work | Coverage cleaning while SLAM-mapping and exploring |
| Auto cleaning | | In progress | Clean the entire room using an existing map (using coverage path planning) |
| Regression tests | | In progress | Set up simulatior regression test framework (auto cleaning in Gazebo) |
| Localization & navigation on a known map | [nav-localize](./contributions/nav-localize) | In progress | Nav2 nav, AMCL localization, relocalize when lost, resume map |
| Dock cycle: undock, dock, recharge | [dock-cycle](./contributions/dock-cycle) | Ready to start work | Undock, return-to-dock, precise docking, station services, find dock when lost |
| Recovery behaviors & safety | [recovery-safety](./contributions/recovery-safety) | Ready to start work | Recovery ladder, escalation, pause-and-alert, safety sensors, status reporting |
| Compute benchmark & memory reduction | [compute-benchmark](./contributions/compute-benchmark) | In progress | Measure ROS2/Nav2/SLAM memory, compare composable nodes, and track the 4 GB -> 2 GB target |
| Floor-surface handling & edge cleaning | [floor-care](./contributions/floor-care) | Ready to start work | Wall/edge following, carpet vs hardwood, mop lift/lower |
| Cleaning modes, zones & job orchestration | [cleaning-jobs](./contributions/cleaning-jobs) | Ready to start work | Modes (regular/spot), virtual walls, room segmentation, job splitting + resume |
| Live robot bring-up & validation | [live-robot-bringup](./contributions/live-robot-bringup) | Ready to start work | Connect the placeholder Proscenic M6 Pro to ROS2, re-run sim tests on hardware |
| Source 3D models (STEP) for BOM parts | [source-3d-models](./contributions/source-3d-models) | In progress | Obtain / measure / model STEP files of off-the-shelf parts (wheels, fans, caster…) so mounts fit |
| Procure part specs & datasheets | [part-specs](./contributions/part-specs) | In progress | Find/measure/reverse-engineer specs (pinouts, encoder PPR, torque, how to drive fans…) for sourced parts |
| I/O + motor-driver PCB | [io-pcb](./contributions/io-pcb) | In progress | I/O board with CM4/CM5 socket, STM32G070 MCU - motors, sensors, 4S2P charging, safety, FreeRTOS, custom serial to CM4/CM5, 2D LiDAR header, IMU, audio serial/amp/speaker, MIPI camera(s) i/f; KiCad, JLCPCB |
| Fit software into 2GB RAM | [compute-benchmark](./contributions/compute-benchmark) | In progress | ROS2 node composition, Rust; remove Gazebo, desktop UI |
> Planned and on-hold modules (mechanical design, later-phase software) live in the
> [RFC backlog](docs/RFC_BACKLOG.md).
## Source code reference
- [OOMWOO ROS2 and Ubuntu installation](https://github.com/makerspet/oomwoo-install/) source code
- [OOMWOO ROS2 URDF package and config](https://github.com/makerspet/oomwoo_urdf/) source code
- [remakeai reference vacuum teardown](https://github.com/remakeai/vacuum-cleaner-teardown) — a consumer LiDAR vacuum with a basic dock and stationary mop.
## Related prior art
- [AlieksieievYurii/vacuum-cleaner](https://github.com/AlieksieievYurii/vacuum-cleaner) — a DIY 3D-printed robot vacuum (Raspberry
Pi Zero W, gyroscope-based, Fusion 360, Android control app, no dock)
- [kaiaai/LDS](https://github.com/kaiaai/LDS), [kaiaai/lds2d](https://github.com/kaiaai/lds2d) — open-source 2D LiDAR libraries (C++, Python) supporting 23+ LiDAR models
- [remakeai/vacuum_ros2_bridge](https://github.com/remakeai/vacuum_ros2_bridge) — ROS2 bridge for a 3irobotix CRL-200-based vacuum (Proscenic), full ROS2 control
- [Valetudo](https://github.com/Hypfer/Valetudo) — cloud-free firmware replacement for commercial vacuums (local app-level control, not ROS2)
- [Dennis Giese / robotinfo.dev](https://robotinfo.dev) — teardowns and rootability of commercial robot vacuums.
- [codetiger/VacuumTiger](https://github.com/codetiger/VacuumTiger) - 3irobotix CRL-200-based vacuum low-level control reverse engineered
- [Build a ROS2/LiDAR robot crash course](https://makerspet.com/blog/build-arduino-self-driving-robot-video-instructions/) - watch this if you have no robotics experience
- [Open Mower](openmower.de) - open-source outdoor lawn mower
## Design research
We reviewed the 20252026 consumer robot vacuum landscape (global + China-sourceable
brands, all price tiers) to decide which solutions to copy and which to skip. Key
takeaways for the build:
- *Suction is a sourcing problem, not an engineering one.* Real-world cleaning does
*not* track advertised suction (Pa); ~$500 mid-tier models beat flagships. A
moderate *sealed* sourced motor + a good brush + tight airflow sealing matches
flagships — *no custom impeller needed.*
- *"Never gets stuck" needs camera + AI sensor fusion*, not LiDAR alone — LiDAR is
blind below its ~10 cm turret (cables, socks). v1 leans on the *bumper* for low
obstacles; vision-based avoidance is a later / experimental goal, not an MVP promise.
- *Anti-tangle brush:* a *tapered rubber roller* resists hair-wrap best (a top user
complaint) and is easy to 3D-print.
- *Mop:* a 3D-printed *dual-spinning* mop is competitive; the self-washing roller
mop's edge is overstated and hard to replicate — skip it for now.
*Well-loved models worth studying:* Eufy Omni S2 (obstacle avoidance), Narwal Flow
(roller mop), Ecovacs Deebot T90 Pro Omni (~$499 all-rounder), Dreame X40 Ultra
(dual-spinning mop). *Dreame* is also the most [Valetudo](https://github.com/Hypfer/Valetudo)-rootable
brand — the safest donor to study. *(Per-model rankings are directional, from
single-run reviewer tests.)*
## About
The project name "OOMWOO" is a rotational ambigram - it reads the same flipped 180°, like the robot itself, roaming your floor in every direction.
The project is sponsored by makerspet.com and remake.ai. We are reusing their open-source solutions.
- If you'd rather skip the parts hunt, a kit (motors, PCB, brushes, gaskets, LiDAR) will be available at [makerspet.com](https://makerspet.com), from the same maker behind this project. The kit is a convenience, never a requirement. *Everything here stays open.*
- When we get to apps, [remake.ai](https://remake.ai) will be providing its robot apps platform and app store. Using the app store will be entirely optional. The vacuum will *always support cloud-free, local operation for regular functionality out-of-the-box*.
## License
Code is released under the [Apache License 2.0](LICENSE).
Hardware design files, once added, to be released under an open hardware
license (TBD).
<a href="https://www.star-history.com/?type=date&repos=makerspet%2Foomwoo">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=makerspet/oomwoo&type=date&theme=dark&legend=top-left&sealed_token=_pwRsmK4mVgCA-wKPZeTQOv6tMzrsQLXFfDoOVMTu1hralpzmceqsNPdBJLLFUfct1DSWAvFA9QaH7KIYC5aiVuC6IXHO76GC8BQlLPlJZB67Vvj6AiwS9neO5174BaYtLDETkKmv9_M8IYiGhFSHHUf29kTBt5pUhW6HQcLpPjQ0GufF2KDPONMJdzV" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=makerspet/oomwoo&type=date&legend=top-left&sealed_token=_pwRsmK4mVgCA-wKPZeTQOv6tMzrsQLXFfDoOVMTu1hralpzmceqsNPdBJLLFUfct1DSWAvFA9QaH7KIYC5aiVuC6IXHO76GC8BQlLPlJZB67Vvj6AiwS9neO5174BaYtLDETkKmv9_M8IYiGhFSHHUf29kTBt5pUhW6HQcLpPjQ0GufF2KDPONMJdzV" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=makerspet/oomwoo&type=date&legend=top-left&sealed_token=_pwRsmK4mVgCA-wKPZeTQOv6tMzrsQLXFfDoOVMTu1hralpzmceqsNPdBJLLFUfct1DSWAvFA9QaH7KIYC5aiVuC6IXHO76GC8BQlLPlJZB67Vvj6AiwS9neO5174BaYtLDETkKmv9_M8IYiGhFSHHUf29kTBt5pUhW6HQcLpPjQ0GufF2KDPONMJdzV" />
</picture>
</a>
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`makerspet/oomwoo`
- 原始仓库:https://github.com/makerspet/oomwoo
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+87
View File
@@ -0,0 +1,87 @@
# First Clean: coverage cleaning with mapping and exploration (ROS2 package)
A ROS2 package for the robot's *first clean*. The vacuum starts with *no map*,
cleans the whole reachable floor using coverage path planning, builds a map with
SLAM *while* it cleans, and keeps exploring until the map is complete. Because the
physical robot isn't built yet, this is a *Gazebo simulation*.
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
> *Scope.* This RFC is only the *first clean from scratch*. Operating on a *saved* map,
> docking, recovery, floor-surface handling, and cleaning modes are deliberately *out of
> scope* and live in their own RFCs:
> [nav-localize](../nav-localize) (navigate / localize / resume a saved map),
> [dock-cycle](../dock-cycle) (undock / dock / recharge),
> [recovery-safety](../recovery-safety) (recovery & safety),
> [floor-care](../floor-care) (wall/edge following, carpet vs hardwood, mop), and
> [cleaning-jobs](../cleaning-jobs) (modes, zones, job orchestration).
> Keep this package focused on producing a *complete map* and *full first-pass coverage*;
> the others build on top of it.
# Important References
- [urdf-gazebo-sim RFC](../urdf-gazebo-sim) — provides the robot URDF, the Gazebo world(s), and the *bumper* this package depends on.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- [m-explore-ros2 (kaiaai fork)](https://github.com/kaiaai/m-explore-ros2) — frontier exploration, tested and working. It maps and explores but does *not* clean — a good starting point to build on.
- [m-explore-ros2 demo + step-by-step instructions (video)](https://www.youtube.com/watch?v=81-9q7QfkHs&list=PLOSXKDW70aR8uA1IFahSKVuk5ODDfjTZV) — shows m-explore-ros2 in action and how to run it.
- [Gazebo simulation setup instructions](https://makerspet.com/blog/tutorial-map-navigate-ros2-robot-in-simulation/) — a simple differential-drive robot with a LiDAR in a Gazebo living room world; another possible starting point.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- reproduce the baseline simulation first
- follow the [Gazebo simulation setup instructions](https://makerspet.com/blog/tutorial-map-navigate-ros2-robot-in-simulation/) to run the diff-drive + LiDAR robot in the Living Room world
- get [m-explore-ros2](https://github.com/kaiaai/m-explore-ros2) running for frontier exploration (see the [demo video](https://www.youtube.com/watch?v=81-9q7QfkHs&list=PLOSXKDW70aR8uA1IFahSKVuk5ODDfjTZV)) — this gives you mapping + exploration *without* cleaning
- build on the [urdf-gazebo-sim RFC](../urdf-gazebo-sim) robot model, world(s) and bumper
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- add coverage cleaning
- the robot starts with *no prior map*
- plan and execute a *coverage path* that cleans the whole reachable floor (e.g. boustrophedon / back-and-forth, plus corner and wall-edge handling)
- build the map with *SLAM while cleaning* (e.g. slam_toolbox or cartographer) — mapping and cleaning happen together, not in separate passes
- keep *exploring frontiers* until the map is complete, so no reachable area is missed
- define and document a clear *done* condition (full coverage *and* complete map), then save the map
- make it robust
- *dynamic obstacles:* a person, pet, or object moving into the robot's path must not break coverage or mapping — the robot should replan and continue
- *LiDAR-invisible static obstacles:* the LiDAR may report open floor the robot cannot actually reach (glass, objects below the LiDAR plane, thresholds, ledges). Detect these via *bumper* contact, mark them as obstacles, recover, and replan around them
- react to *bumper events* (left switch, right switch, front bumper) published by the urdf-gazebo-sim bumper
- never get permanently stuck — recover from collisions and wedged situations (a basic local recovery here; the full recovery ladder lives in [recovery-safety](../recovery-safety))
- test it well
- start the robot from *various initial locations* and verify it still achieves full coverage and a complete map
- add *regression tests* (headless, CI-friendly) that verify both:
- *map completeness* — the built map covers the whole reachable area
- *coverage completeness* — the cleaning path covers the whole reachable floor
- test recovery from dynamic obstacles and from bumper hits on LiDAR-invisible obstacles
- additional Gazebo worlds (highly valued)
- create and test extra worlds: *multiple rooms, different floorplans*, narrow passages, furniture, and LiDAR-invisible obstacles
- contribute these worlds back — they help everyone test
- submit a PR (pull request) to `contributions/clean-and-map/<your-github-username>/`
- link to ROS2 package(s)
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- the Gazebo worlds you added
- videos of full clean-and-map runs from several start poses
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- Starting with no map in the Living Room world, from *multiple initial poses*, the robot:
- achieves *full coverage* of the reachable floor
- builds a *complete map* of the reachable area
- detects a clear done condition and saves the map
- Robust to *dynamic obstacles* — a moving obstacle in the path does not break coverage or mapping
- Robust to *LiDAR-invisible static obstacles* — bumper contacts are detected, the obstacle is marked and avoided, the robot replans and does not get stuck
- Reacts correctly to *left / right / front bumper* events
- *Regression tests* pass and verify both map completeness and coverage completeness, runnable headless in CI
- Works in at least one *additional multi-room / different floorplan* world
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+71
View File
@@ -0,0 +1,71 @@
# Cleaning Modes, Zones & Job Orchestration (ROS2 package)
The user-facing *"what and where to clean,"* plus managing *long jobs*. This
package adds cleaning *modes* (regular whole-floor, spot), *virtual walls /
no-go zones*, *segmenting the map into rooms*, and a *job orchestrator* that
splits a clean into manageable pieces — pausing to *recharge, auto-empty, or wash
the mop*, then *resuming where it left off*. Because the physical robot isn't
built yet, this is a *Gazebo simulation*; it is later re-validated on hardware in
the [live-robot-bringup RFC](../live-robot-bringup).
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
# Important References
- [clean-and-map RFC](../clean-and-map) — coverage cleaning and its done condition; this orchestrates and segments it.
- [nav-localize RFC](../nav-localize) — the saved map this segments and the resume-after-interruption support.
- [dock-cycle RFC](../dock-cycle) — the recharge / auto-empty / mop-wash station services a job pauses for.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- Nav2 keepout/zone costmap filters are a good starting point for no-go zones.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- *map segmentation into rooms / zones*
- split a saved map into rooms/zones (automatic segmentation plus manual labeling) and persist it
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- *cleaning modes*
- *regular* (whole map, or selected rooms), *spot* (clean a small local area), configurable
- defer wall/edge mode to [floor-care](../floor-care); call it where useful
- *virtual walls / no-go zones*
- define keep-out regions the planner respects (e.g. Nav2 keepout filter); editable and persisted
- *job orchestration (split a clean into manageable pieces)*
- monitor battery / dust bin / mop state during a clean
- *suspend* the job and dock for *recharge / auto-empty / mop-wash* (via [dock-cycle](../dock-cycle)), then *resume coverage exactly where it stopped* (coverage memory; ties to [clean-and-map](../clean-and-map) + [nav-localize](../nav-localize) resume)
- guarantee the full job still reaches full coverage across one or more interruptions
- *job interface*
- an action/service API to *start / pause / resume / cancel* a job and report *status*, suitable for Home Assistant
- test it well
- multi-room worlds; verify whole-map, per-room, and spot jobs; verify no-go zones are respected
- force a recharge / auto-empty / mop-wash mid-job and verify the job resumes to *full coverage*
- regression tests (headless, CI-friendly)
- per-room / spot / whole-map jobs clean exactly the intended area
- no-go zones are never entered
- *coverage completeness is preserved across forced interruptions*
- submit a PR (pull request) to `contributions/cleaning-jobs/<your-github-username>/`
- link to ROS2 package(s)
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- videos of segmented/spot cleaning and a job interrupted by a dock service then resumed
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- *Modes* work: whole-map, per-room, and spot cleaning each clean exactly the intended area
- *Virtual walls / no-go zones* are defined, persisted, and never entered
- The map is *segmented into rooms* that can be targeted individually
- A long job *splits into pieces* — suspends for recharge / auto-empty / mop-wash and *resumes to full coverage*
- A *start / pause / resume / cancel / status* interface exists and is Home-Assistant-friendly
- *Regression tests* pass, including coverage-preserved-across-interruptions, runnable headless in CI
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+86
View File
@@ -0,0 +1,86 @@
# Compute Benchmark & Memory Reduction
Measure whether OOMWOO can keep ROS2/Nav2/SLAM onboard while reducing the
minimum compute target from a comfortable 4 GB class system toward a practical
2 GB class system.
This module is for repeatable measurements, not guesses. It should help compare
Python/C++ ROS2 nodes, ROS2 composable-node layouts where supported, process
layout changes, and later optional Rust experiments under the same workload.
> Status: in progress. Use the ROS2 Jazzy development container and the current
> simulation stack where possible. Hardware runs on Pi 4, CM4, CM5, or compatible
> modules are especially useful when available.
## Current Working Assumptions
- The consumer vacuum profile keeps SLAM and navigation onboard.
- The practical near-term target is Pi 4 / CM4-class compute with 4 GB RAM.
- The stretch target is to reduce the minimum memory requirement toward 2 GB
without giving up ROS2.
- The compute-module direction is CM4/CM5 or compatible modules on a carrier
board; ignore the older RK3562 reference path.
- The MCU owns motors, sensors, safety, battery/charging supervision, watchdogs,
and the custom serial protocol.
- STM32G070RBT6 is a strong MCU candidate because of its GPIO/ADC count, low
cost, and LQFP manufacturability, while the MCU choice remains reviewable.
- The MVP 2D LiDAR target is 5 Hz with no scan dropping.
- Rust/rclrs is an optional late-summer integration candidate, not a baseline
dependency today.
Context: this module follows the compute discussion in
[issue #18](https://github.com/makerspet/oomwoo/issues/18).
## Request For Contribution
Submit a benchmark contribution under:
```text
contributions/compute-benchmark/<your-github-username>/
```
Include:
- a reproducible benchmark plan
- scripts that capture RSS/PSS/CPU/startup timing where possible
- a hardware/software run matrix
- a compute BOM table with regional price and stock fields
- clear notes on ROS distro, RMW, hardware, RAM size, LiDAR rate, and scenario
- a short decision record after measurements
## Metrics
Minimum useful metrics:
| Metric | Why it matters |
|---|---|
| RSS/PSS per process | Shows where memory is actually going. |
| CPU idle / mapping / navigation | Separates always-on cost from workload cost. |
| Startup time | Exposes heavy node initialization and launch overhead. |
| LiDAR update rate | Confirms the benchmark is not cheating by dropping scans. |
| Nav2/SLAM headroom | Determines whether 2 GB is plausible with ROS2 retained. |
| Recovery-event latency | Keeps safety-adjacent nodes honest under optimization. |
## Strategies To Compare
1. Current Python/C++ baseline.
2. ROS2 composable nodes where supported, plus launch/process layout changes.
3. C++/rclcpp for selected hot or always-on custom nodes.
4. Optional Rust/rclrs spike for selected custom nodes after dev-image setup is
reproducible.
Rust should only move from optional to recommended if it demonstrates a useful
RSS/PSS or latency improvement and does not make the default Jazzy developer
experience fragile.
## Acceptance Criteria
- Measurements are reproducible by another contributor.
- The benchmark records hardware, RAM, ROS distro, RMW, scenario, and git SHA.
- Results distinguish process RSS from PSS when PSS is available.
- Benchmarks do not reduce LiDAR update rate below the target unless explicitly
marked as an experiment.
- The contribution explains whether the result supports 4 GB only, 2 GB as a
stretch target, or a different hardware class.
- BOM notes include module/board, carrier, power, cooling, storage, MCU add-ons,
regional price, and stock status.
@@ -0,0 +1,100 @@
# Compute Benchmark Plan by xbattlax
This contribution turns the compute discussion in issue #18 into a concrete
benchmark scaffold for OOMWOO.
The immediate goal is to measure whether OOMWOO can keep onboard ROS2, Nav2, and
SLAM while reducing the practical memory target from 4 GB toward 2 GB. The first
optimization path should be ROS2 composable nodes where supported, plus
launch/process layout. Rust with rclrs is kept as a serious optional spike for
late August / September 2026, gated by measurements and dev-image
reproducibility.
## Scope
Included:
- a Linux `/proc`-based process sampler for RSS/PSS/CPU
- a run matrix template for repeatable benchmark scenarios
- a compute BOM template for board/module, carrier, power, cooling, storage,
MCU add-ons, regional pricing, and stock status
- a short architecture decision record for the current memory-reduction plan
Not included yet:
- a Rust/rclrs node implementation
- a C++/rclcpp equivalent node
- physical Pi 4 / CM4 / CM5 results
- automated launch files for full SLAM/Nav2 runs
## Hardware Profiles To Record
| Profile | Purpose | Notes |
|---|---|---|
| Dev machine | Reference only | Useful for repeatability, not a robot target. |
| Pi 4 4 GB | Minimum prior-art target | Validate onboard SLAM/Nav2 feasibility. |
| CM4 4 GB | Consumer carrier-board target | Low profile and product-friendly. |
| CM5 4 GB+ | Higher headroom target | Useful if camera/NPU alternatives are evaluated. |
| 2 GB class SBC/module | Stretch target | Requires real measured headroom, not assumptions. |
| ESP32 educational profile | Offboard ROS2/SLAM learning setup | Not a consumer autonomous vacuum profile. |
## Measurement Scenarios
Start with these scenarios:
1. ROS2 graph idle after launch.
2. SLAM running with 5 Hz LiDAR input and no scan dropping.
3. Nav2 navigating on a known map.
4. Recovery/safety node idle.
5. Recovery/safety event burst.
6. Same workload after composable-node or process-layout changes.
7. Later: same selected custom node in Python, C++/rclcpp, and Rust/rclrs.
## Using The Sampler
Run this inside the Linux ROS2 environment while the target workload is already
running:
```bash
bash contributions/compute-benchmark/xbattlax/scripts/measure_ros_processes.sh \
--pattern 'ros2|component_container|python3|slam_toolbox|nav2' \
--duration 60 \
--interval 2 \
--label slam_5hz_baseline \
--output /tmp/oomwoo-slam-5hz-baseline.csv
```
The script writes CSV with:
- timestamp
- sample index
- label
- PID
- process name
- CPU percent
- RSS KiB
- PSS KiB when `/proc/<pid>/smaps_rollup` is available
- command line
PSS is usually more useful than RSS for ROS2 systems because shared libraries and
middleware pages can make RSS look worse than the actual proportional memory
pressure.
## Templates
- `templates/run_matrix.csv`: planned benchmark runs and environment details.
- `templates/compute_bom.csv`: compute BOM and regional stock/price snapshot.
Copy the templates into a results folder for real benchmark submissions. Do not
edit old result rows after a run; add a new row for a new measurement.
## Expected Decision Output
Each benchmark batch should end with a short note:
- current measured minimum RAM class
- biggest memory users
- whether composable nodes helped
- whether any Python node is worth porting to C++ or Rust
- whether Rust/rclrs setup is reproducible enough to keep testing
- what hardware profile the result supports
@@ -0,0 +1,58 @@
# ADR 0001: Memory-Reduction Strategy
## Status
Proposed.
## Context
OOMWOO should remain approachable for builders, so the first consumer vacuum
profile should run SLAM and navigation onboard. Requiring a separate workstation
for mapping would make the regular product harder to build and use.
The maintainer has clarified these current assumptions:
- Pi 4 / CM4-class compute with 4 GB RAM is a realistic near-term baseline.
- Reducing toward 2 GB while keeping ROS2 would be valuable.
- CM4/CM5 or compatible modules are the compute-module direction.
- The older RK3562 reference schematic should be ignored for new compute-module
planning.
- The MCU role is fixed around motors, sensors, safety, battery/charging
control, watchdogs, and custom serial protocol.
- ESP32/micro-ROS is better suited to an educational/offboard profile than to
the consumer safety/controller role.
## Decision
Use this priority order for memory-reduction work:
1. Measure the current Python/C++ ROS2 baseline.
2. Try ROS2 composable nodes where supported, plus launch/process layout
improvements first.
3. Consider C++/rclcpp for selected memory-heavy or latency-sensitive custom
nodes when composable layout is insufficient.
4. Keep Rust/rclrs as an optional late-summer 2026 spike for selected custom
nodes, not as a baseline dependency today.
Rust/rclrs should be evaluated seriously, but only promoted if:
- RSS/PSS or latency wins are meaningful against Python and C++ baselines
- the Jazzy build/setup is reproducible in the OOMWOO dev image
- contributor onboarding remains reasonable
- the node selected for porting is small enough to keep the experiment bounded
## Consequences
- The first benchmark contribution can avoid adding Rust dependencies.
- The benchmark still leaves a clear path for a later rclrs experiment.
- The project can pursue the 4 GB -> 2 GB target with lower dependency risk.
- Results should identify whether memory pressure comes from custom Python
nodes, ROS2 process layout, Nav2/SLAM, or other runtime overhead.
## Open Questions
- Which launch graph should be the canonical benchmark workload?
- Which ROS2 RMW should be the baseline for memory measurements?
- Which node is the best first Rust/rclrs candidate if measurements justify it?
- How should PSS be collected on target hardware when permissions differ?
- What minimum headroom should qualify a 2 GB target as realistic?
@@ -0,0 +1,208 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
measure_ros_processes.sh --pattern REGEX [options]
Options:
--pattern REGEX Process command-line regex to sample. Required.
--duration SECONDS Total sample duration. Default: 30.
--interval SECONDS Seconds between samples. Default: 1.
--label LABEL Free-form label written to each row. Default: run.
--output FILE CSV output path. Default: stdout.
--help Show this help.
Examples:
bash measure_ros_processes.sh \
--pattern 'ros2|component_container|python3|slam_toolbox|nav2' \
--duration 60 \
--interval 2 \
--label slam_5hz_baseline \
--output /tmp/oomwoo-slam-5hz-baseline.csv
Notes:
This script is intended for Linux ROS2 targets. It reads /proc for RSS and
PSS. PSS is blank when /proc/<pid>/smaps_rollup is not readable.
EOF
}
pattern=""
duration="30"
interval="1"
label="run"
output=""
while [[ $# -gt 0 ]]; do
case "$1" in
--pattern)
pattern="${2:-}"
shift 2
;;
--duration)
duration="${2:-}"
shift 2
;;
--interval)
interval="${2:-}"
shift 2
;;
--label)
label="${2:-}"
shift 2
;;
--output)
output="${2:-}"
shift 2
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ -z "$pattern" ]]; then
echo "--pattern is required" >&2
usage >&2
exit 2
fi
if [[ ! -d /proc ]]; then
echo "This sampler requires Linux /proc." >&2
exit 1
fi
if ! [[ "$duration" =~ ^[0-9]+$ ]] || [[ "$duration" -lt 1 ]]; then
echo "--duration must be a positive integer" >&2
exit 2
fi
if ! [[ "$interval" =~ ^[0-9]+$ ]] || [[ "$interval" -lt 1 ]]; then
echo "--interval must be a positive integer" >&2
exit 2
fi
csv_escape() {
local value="${1:-}"
value="${value//$'\n'/ }"
value="${value//$'\r'/ }"
value="${value//\"/\"\"}"
printf '"%s"' "$value"
}
read_status_kib() {
local pid="$1"
local key="$2"
awk -v key="$key" '$1 == key ":" { print $2; found=1; exit } END { if (!found) print "" }' "/proc/$pid/status" 2>/dev/null || true
}
read_pss_kib() {
local pid="$1"
if [[ -r "/proc/$pid/smaps_rollup" ]]; then
awk '$1 == "Pss:" { print $2; found=1; exit } END { if (!found) print "" }' "/proc/$pid/smaps_rollup" 2>/dev/null || true
fi
}
read_cmdline() {
local pid="$1"
if [[ -r "/proc/$pid/cmdline" ]]; then
tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | sed 's/[[:space:]]*$//'
fi
}
read_comm() {
local pid="$1"
if [[ -r "/proc/$pid/comm" ]]; then
tr -d '\n' < "/proc/$pid/comm" 2>/dev/null
fi
}
read_cpu_percent() {
local pid="$1"
ps -p "$pid" -o %cpu= 2>/dev/null | awk '{ print $1 }'
}
emit_header() {
printf 'timestamp_utc,sample_index,label,pid,comm,cpu_percent,rss_kib,pss_kib,cmdline\n'
}
emit_row() {
local timestamp="$1"
local sample_index="$2"
local pid="$3"
local comm="$4"
local cpu_percent="$5"
local rss_kib="$6"
local pss_kib="$7"
local cmdline="$8"
printf '%s,%s,' "$timestamp" "$sample_index"
csv_escape "$label"
printf ',%s,' "$pid"
csv_escape "$comm"
printf ',%s,%s,%s,' "$cpu_percent" "$rss_kib" "$pss_kib"
csv_escape "$cmdline"
printf '\n'
}
run_sampler() {
local sample_index=0
local started="$SECONDS"
emit_header
while (( SECONDS - started <= duration )); do
local timestamp
timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
mapfile -t pids < <(
for proc in /proc/[0-9]*; do
pid="${proc##*/}"
cmdline="$(read_cmdline "$pid")"
if [[ -n "$cmdline" && "$cmdline" =~ $pattern ]]; then
printf '%s\n' "$pid"
fi
done
)
if [[ "${#pids[@]}" -eq 0 ]]; then
emit_row "$timestamp" "$sample_index" "" "no_process_match" "" "" "" ""
else
local pid
for pid in "${pids[@]}"; do
if [[ ! -d "/proc/$pid" ]]; then
continue
fi
emit_row \
"$timestamp" \
"$sample_index" \
"$pid" \
"$(read_comm "$pid")" \
"$(read_cpu_percent "$pid")" \
"$(read_status_kib "$pid" VmRSS)" \
"$(read_pss_kib "$pid")" \
"$(read_cmdline "$pid")"
done
fi
sample_index=$((sample_index + 1))
if (( SECONDS - started >= duration )); then
break
fi
sleep "$interval"
done
}
if [[ -n "$output" ]]; then
mkdir -p "$(dirname "$output")"
run_sampler > "$output"
else
run_sampler
fi
@@ -0,0 +1,7 @@
hardware_profile,role,part,sku_or_module,quantity,unit_price,currency,regional_source_url,stock_status,power_notes,cooling_notes,storage_or_carrier_notes,notes
CM4_4GB,compute,Compute Module 4,,,,,,,,,,
CM5_4GB,compute,Compute Module 5,,,,,,,,,,
Pi4_4GB,compute,Raspberry Pi 4 4GB,,,,,,,,,,
TwoGB_stretch,compute,2GB SBC or module,,,,,,,,,,
Consumer_MCU,safety_controller,STM32G070RBT6,,,,,,,,,,
Educational_ESP32,micro_ros_bridge,ESP32 board,,,,,,,,,,
1 hardware_profile role part sku_or_module quantity unit_price currency regional_source_url stock_status power_notes cooling_notes storage_or_carrier_notes notes
2 CM4_4GB compute Compute Module 4
3 CM5_4GB compute Compute Module 5
4 Pi4_4GB compute Raspberry Pi 4 4GB
5 TwoGB_stretch compute 2GB SBC or module
6 Consumer_MCU safety_controller STM32G070RBT6
7 Educational_ESP32 micro_ros_bridge ESP32 board
@@ -0,0 +1,8 @@
run_id,date_utc,git_sha,hardware_profile,ram_gb,ros_distro,rmw,scenario,node_strategy,lidar_hz,scan_dropping,command_or_launch,expected_duration_s,notes
baseline_idle,,,,,jazzy,,ros_graph_idle,python_cpp_baseline,5,no,,60,
slam_5hz,,,,,jazzy,,slam_mapping,python_cpp_baseline,5,no,,300,
nav_known_map,,,,,jazzy,,nav2_known_map,python_cpp_baseline,5,no,,300,
recovery_idle,,,,,jazzy,,recovery_safety_idle,python_cpp_baseline,5,no,,60,
recovery_burst,,,,,jazzy,,recovery_safety_event_burst,python_cpp_baseline,5,no,,60,
composable_idle,,,,,jazzy,,ros_graph_idle,composable_nodes,5,no,,60,
rust_spike_late_summer,,,,,jazzy,,selected_custom_node,rust_rclrs_optional,5,no,,60,
1 run_id date_utc git_sha hardware_profile ram_gb ros_distro rmw scenario node_strategy lidar_hz scan_dropping command_or_launch expected_duration_s notes
2 baseline_idle jazzy ros_graph_idle python_cpp_baseline 5 no 60
3 slam_5hz jazzy slam_mapping python_cpp_baseline 5 no 300
4 nav_known_map jazzy nav2_known_map python_cpp_baseline 5 no 300
5 recovery_idle jazzy recovery_safety_idle python_cpp_baseline 5 no 60
6 recovery_burst jazzy recovery_safety_event_burst python_cpp_baseline 5 no 60
7 composable_idle jazzy ros_graph_idle composable_nodes 5 no 60
8 rust_spike_late_summer jazzy selected_custom_node rust_rclrs_optional 5 no 60
+73
View File
@@ -0,0 +1,73 @@
# Dock Cycle: undock, dock, recharge & station services (ROS2 package)
Everything around the charging/service *dock*. The robot must *undock* at the
start of a job, *return to the dock* (on completion or on low battery), *dock
precisely* to make charge contact, trigger *dock station services* (recharge,
and — where the dock supports them — auto-empty and mop-wash), and *find the dock
when it is lost* and relocalization has already failed. Because the physical robot
isn't built yet, this is a *Gazebo simulation*; it is later re-validated on
hardware in the [live-robot-bringup RFC](../live-robot-bringup).
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
# Important References
- [nav-localize RFC](../nav-localize) — localization + Nav2; the find-the-dock fallback is invoked when its relocalization fails.
- [clean-and-map RFC](../clean-and-map) and [cleaning-jobs RFC](../cleaning-jobs) — trigger return-to-dock for recharge / auto-empty / mop-wash mid-job.
- [urdf-gazebo-sim RFC](../urdf-gazebo-sim) — robot URDF and Gazebo world(s) to model the dock in.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- Model a *generic basic charging dock* (charge contacts + a detectable marker). Exact dock geometry is TBD — the old teardown reference vacuum is no longer used.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- Nav2 docking (`opennav_docking`) is a good starting point for precise approach.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- model the dock in Gazebo
- add charge contacts and a *detectable marker* for the final approach (IR beacon, fiducial/AprilTag, or a reflective-intensity pattern the LiDAR can see)
- model a *battery* (drain during cleaning, charge curve while docked) so low-battery behavior can be tested
- model a *generic basic dock* (contacts + a detectable marker); exact geometry TBD
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- undocking
- safely back / drive out of the dock to a known start pose, then hand off to cleaning
- return-to-dock
- navigate to the dock vicinity, triggered by *job complete* OR *low battery / full bin / mop needs washing* mid-job (coordinate with [cleaning-jobs](../cleaning-jobs))
- precise docking
- final approach using the dock marker; align to the charge contacts; *confirm charging started*
- dock station services
- once docked, trigger and wait for *recharge*, and — where the dock supports them — *auto-empty* and *mop-wash*; report completion so the job can resume
- find-the-dock-when-lost
- fallback used when [nav-localize](../nav-localize) relocalization has *failed*: run a search pattern to reacquire the dock marker, dock, and re-establish pose from the known dock location
- test it well
- start from *various poses and battery levels*; verify undock → clean → return → dock → service → resume
- *kidnap* the robot so relocalization fails and verify find-the-dock recovers it
- regression tests (headless, CI-friendly)
- docking success rate and final alignment error
- find-the-dock success rate from random lost poses
- low-battery return-then-resume completes without losing the job
- submit a PR (pull request) to `contributions/dock-cycle/<your-github-username>/`
- link to ROS2 package(s) and the dock model / world additions
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- videos of docking, find-the-dock, and low-battery return-and-resume
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- Robot *undocks* reliably to a known start pose
- Robot *returns and docks precisely*, making charge contact, from various poses (alignment within a stated tolerance)
- *Station services* (recharge; auto-empty / mop-wash where modeled) are triggered, completed, and reported
- *Low-battery mid-job* triggers return-to-dock and the job resumes after charging
- *Find-the-dock-when-lost* recovers a kidnapped robot after relocalization fails
- *Regression tests* pass (dock success, alignment error, find-the-dock success), runnable headless in CI
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+33
View File
@@ -0,0 +1,33 @@
# Dust Bin (mechanical module)
> *On hold.* The project moved *away from the old teardown reference vacuum*, so the
> dust bin must be designed around the *sourced parts + a forthcoming 3D reference design*
> (not yet sketched). This module resumes once the key parts are sourced and the design is
> sketched. *Useful now:* [source-3d-models](../source-3d-models) and [part-specs](../part-specs).
A removable dust bin that collects dust, holds an air filter, and receives air from the
vacuum fan. The bin inlet mates with a gasket; the lid latch is spring-loaded.
# Request for Contribution — Instructions (resumes when off hold)
When this reopens, design a 3D-printable removable dust bin that:
- mates (sealed) with the *vacuum-fan inlet* and the chassis
- holds a *common, replaceable filter* — standardise on an abundant filter size so builders
can rebuy anywhere (see [design research](../../README.md#design-research))
- has a reliable *spring latch*; easy to remove, insert, and empty
- is 3D-printable (assume PETG); split to fit ~20 × 25 cm, 20 cm height
- ignore mop functionality for now
- submit STEP + native CAD + 3MF/STL + a sub-component BoM + docs/photos to
`contributions/dust-bin/<your-github-username>/`
## Acceptance criteria (when it resumes)
- *Sealed* airflow (no leaks), easy to empty, reliable latch
- Fits the (forthcoming) chassis + fan interfaces without forcing changes to other modules
- Uses a common, sourceable filter size
- Documented; STEP + native CAD source provided
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple attempts
are welcome and useful even if not selected — modules are swappable, and a non-selected
design is still a valid learning exercise and a fallback.
+69
View File
@@ -0,0 +1,69 @@
# Floor-Surface Handling & Edge Cleaning (ROS2 package)
Clean *better* by adapting to the floor. This package adds *wall following* (to
clean tight against walls), *carpet-edge following*, recognizing *carpet vs
hardwood*, and *lifting / lowering the mop* accordingly (never wet a carpet).
Because the physical robot isn't built yet, this is a *Gazebo simulation* — you
model the surfaces and the surface sensor in Gazebo; it is later re-validated on
hardware in the [live-robot-bringup RFC](../live-robot-bringup).
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
# Important References
- [clean-and-map RFC](../clean-and-map) — coverage cleaning that this refines at edges and surface transitions.
- [urdf-gazebo-sim RFC](../urdf-gazebo-sim) — robot URDF; this package likely needs a *surface sensor* and a *mop lift/lower actuator* modeled.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
> Note: this package may need new sim sensors/actuators (surface sensor, mop
> actuator). Add them inside your submission for now and propose folding the
> stable ones back into [urdf-gazebo-sim](../urdf-gazebo-sim) later — please don't
> rewrite that RFC.
# Request for Contribution - Instructions
- *wall following*
- detect walls from the LiDAR and drive a controlled offset along them so edges get cleaned, not just the room interior
- integrate with [clean-and-map](../clean-and-map) coverage so edges *and* interior are both covered, without re-cleaning everything
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- *carpet-edge following*
- detect carpet boundaries and follow the edge to clean along them
- *surface recognition (carpet vs hardwood)*
- model a plausible sensor in sim (wheel current / IMU vibration / a dedicated downward sensor) and *publish the surface type*
- *mop lift / lower*
- actuate the mop *up on carpet, down on hardwood*; model the actuator in Gazebo; *never wet a carpet*
- coordinate with [cleaning-jobs](../cleaning-jobs) for mop-related job constraints
- test it well
- build worlds with *mixed carpet + hardwood* regions and walls
- verify edge coverage, correct surface classification, and correct mop state at every transition
- regression tests (headless, CI-friendly)
- surface-classification accuracy
- edge-coverage percentage (walls / carpet edges actually cleaned)
- *zero carpet-wetting events* (mop never down on carpet)
- submit a PR (pull request) to `contributions/floor-care/<your-github-username>/`
- link to ROS2 package(s) and any sim sensor/actuator + world additions
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- videos of wall/edge following and mop lift/lower at surface transitions
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- *Wall / carpet-edge following* cleans tight along edges and integrates with coverage (high edge-coverage %)
- *Surface recognition* classifies carpet vs hardwood accurately
- *Mop lift/lower* tracks surface type correctly with *zero carpet-wetting events*
- *Regression tests* pass (classification accuracy, edge coverage, no carpet-wetting), runnable headless in CI
- Works in at least one *mixed carpet + hardwood* world
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+153
View File
@@ -0,0 +1,153 @@
# I/O + Motor-Driver PCB (hardware / KiCad)
The custom board that connects every OOMWOO motor and sensor to the SBC: an STM32 MCU,
motor drivers, sensor front-ends, and battery charging on one PCB. The MCU runs firmware /
micro-ROS and talks to the Raspberry Pi 5 SBC over a serial / USB link.
> *Design basis:* OOMWOO v1 uses a *separate Raspberry Pi 5* as the SBC (ROS2, Nav2, SLAM),
> so this board is a *pure I/O + power board* — no application processor on it. There is a
> starting-point reference schematic (an RK3562 + STM32 combined design), but the on-board
> Rockchip SoC and its whole subsystem are *removed*; only the STM32 I/O side is kept and
> converted to KiCad for review.
# References
- *Starting-point schematic (PDF)* —
[makerspet/oomwoo-io-board](https://github.com/makerspet/oomwoo-io-board/blob/main/oomwoo-io-board-RK3562-schematic.pdf).
An RK3562 + STM32G070 reference (Apache-2.0, *unvalidated* — a starting point, not a proven
design). Trim it down as described below.
- *Drive-wheel connector pinout* —
[AlieksieievYurii vacuum-cleaner motherboard schematic](https://raw.githubusercontent.com/AlieksieievYurii/vacuum-cleaner/2bd7cf7f9af3ae9040373f667bab83e2e57c26b7/motherboard/circuit-pcb/SCHEMATIC_motherboard.svg)
shows the drive-wheel assembly connectors: *JST PH2.0, 6-pin*
| Pin | Signal |
|---|---|
| 1 | MOT+ |
| 2 | MOT- |
| 3 | HALL_SPEED |
| 4 | HALL_DIR |
| 5 | +5V |
| 6 | GND |
Verify against the *sourced* Roborock-family wheel module before layout — a submission in
[part-specs](../part-specs) describes a variant with extra wheel-drop / limit-switch pins,
so confirm the pin count and pinout on a physical module.
- [part-specs](../part-specs) — connector pinouts, encoder PPR, and datasheets for the sourced parts.
- [design-document.md](../../docs/design-document.md) — the I/O-board section (MCU, pin budget,
offloading the fan to an external ESC, etc.).
- [BOM.md](../../BOM.md) — the sourced parts this board must drive.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) · [Discord](https://discord.gg/3y2JKz5T25)
# Request for Contribution — Instructions
Deliver a *KiCad schematic* derived from the reference PDF, trimmed to the I/O side and
updated for OOMWOO, then *hold for review before PCB layout*.
- *Remove the Rockchip subsystem entirely* — the RK3562 SoC and everything that exists only
to support it: *LPDDR4 DRAM, eMMC, the SoC PMIC / SoC-specific power rails (VCCIO, PMU / OSC
/ PLL), the DDR PHY, the USB / PCIe PHY, and the MIPI camera interface*. Heavy compute and
the camera live on the Raspberry Pi 5 SBC, not this board.
- *Remove the WiFi / BT module* (AP6256) — WiFi/BT is provided by the SBC.
- *Keep and convert to KiCad the I/O side:*
- *STM32G070* MCU + support (clock, decoupling, debug / boot, the serial / USB link to the SBC)
- *motor drivers* — drive wheels (×2), main brush, side brush, water pump, mop lift / spin
(if fitted); put the *suction fan on an external ESC* (one PWM line) to save MCU pins
- *sensor front-ends* — cliff / anti-fall IR, docking IR + bumper, side-proximity IR, bumper
switches, IMU, LiDAR interface, multizone ToF, ultrasonic carpet sensor
- *battery charging + protection / BMS*, and the board power rails that feed the STM32 /
sensors / motors (keep these; remove only the SoC-specific rails)
- speaker + amp, mic, buttons, LEDs
- *Move the battery from 3S to 4S* — OOMWOO targets *~14.8 V* (see [BOM.md](../../BOM.md)). The
reference appears to be 3S; update the pack, the charge-IC configuration, the protection, and
any cell-count-dependent dividers / thresholds to *4S*.
- *Wire the drive-wheel connectors* to the JST PH2.0 6-pin pinout above (verified against the
sourced module).
- assume battery Xiaomi/Roborock/Dreame BRR-2P4S-5200
- *Convert the kept design to KiCad* (from Altium); keep a clean, readable, hierarchical schematic.
- *Hold here for review.* Deliver the trimmed, 4S, KiCad *schematic* and stop — the maintainer
reviews before anyone starts PCB layout / manufacturing.
- *Submit* a PR to `contributions/io-pcb/<your-github-username>/` with the KiCad project, a short
sub-BoM, and notes; announce it in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=).
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria (schematic-hold milestone)
- The *entire Rockchip subsystem* (SoC, DRAM, eMMC, PMIC / VCCIO / PMU / PLL, DDR + USB/PCIe
PHY, MIPI camera) and the *WiFi / BT module* are removed.
- The *kept blocks* (STM32G070, motor drivers, sensor front-ends, battery charging, audio,
buttons / LEDs, SBC link) are present, correct, and complete in *KiCad*.
- Battery as specified
- Drive-wheel connectors match the referenced pinout (JST PH2.0 6-pin), reconciled with part-specs.
- Delivered as a buildable *KiCad project*; *ERC clean*; a sub-BoM and short design notes included.
- Stops at the reviewed *schematic* — no PCB layout yet.
- Documented and reproducible.
- TBD, expect criteria to evolve.
The maintainer intends to *accelerate* this module and may commission a contributor to do it;
community submissions are still welcome and reviewed the same way. The maintainer selects among
compliant candidates using these criteria — multiple attempts are welcome and useful even if
not selected.
## Appendix A. Tentative MCU GPIO list
1. Power source current sense (analog in)
2. VBat sense (analog in)
3. Main fan sense (analog in)
4. anti-fall left up sensor (analog in because IR sensors are analog)
5. anti-fall left down sensor (analog in)
6. anti-fall right up sensor (analog in)
7. anti-fall right down sensor (analog in)
8. wheel motor left driver in1 (digital output)
9. wheel motor left driver in2 (digital output)
10. wheel motor left driver encoder (digital input)
11. wheel motor right driver encoder (digital input)
12. Power button (digital input)
13. CPU (e.g. Raspberry Pi) power on/off (digital output)
14. STM32 SWDIO
15. STM32 SWCLK
16. Vacuum power on/off (digital output)
17. Wheel motor right current sense (analog in)
18. Wheel motor left current sense (analog in)
19. Main brush motor current sense (analog in)
20. IMU SPI SCLK (digital out)
21. IMU SPI MISO
22. IMU SPI MOSI
23. IMU SPI CS
24. Wheel motor right driver in1 (digital out)
25. Motors power enable (digital out)
26. Wheel motor right driver in2 (digital out)
27. Water pump sense (analog in)
28. Side brush left front motor sense (analog in)
29. Side brush right front motor sense (analog in)
30. CPU reset (e.g. Raspberry Pi)
31. Dock IR sensor 1 (analog in)
32. Dock IR sensor 2 (analog in)
33. Water pump motor PWM (digital out)
34. Main brush motor PWM (digital out)
35. Lidar motor PWM (digital out)
36. Bumper switch 1 (digital in)
37. UART1 TX
38. UART RX
39. Side brush motor right PWM (digital out)
40. Side brush motor left PWM (digital out)
41. Power LED on/off (digital out)
42. Home LED on/off (digital out)
43. Home button (digital in)
44. Battery charge sense (digital in)
45. Charge status (digital out)
46. Bumper switch 1 (digital in)
47. Bumper switch 2 (digital in)
48. Test/program
49. Test/program
50. Main fan motor PWM (digital out)
51. Main fan motor current sense (analog in)
52. IMU interrupt 2 (digital in)
53. IMU interrupt 1 (digital in)
54. IMU FSYNC (digital in)
55. Side proximity IR sensor left (analog in)
56. Side proximity IR sensor right (analog in)
57. Side proximity IR LED left PWM (digital out)
58. Side proximity IR LED right PWM (digital out)
59. Wheel drop sensor left (digital in)
60. Wheel drop sensor right (digital in)
@@ -0,0 +1,65 @@
# Live Robot Bring-up & Validation (ROS2 integration)
Take the behaviors validated in *simulation* and run them on a *real robot
vacuum*. Connect an off-the-shelf vacuum to ROS2 following the Proscenic M6 Pro
tutorial, then *re-run the acceptance tests from the simulation RFCs on
hardware*. This is the *single home for live validation* across all behaviors,
so the many sim contributors aren't blocked on hardware and the bring-up / bridge
cost is paid once.
> *Note:* the Proscenic / 3irobotix CRL-200S here is an *interim real-hardware test mule*
> for the ROS2 software stack — it is *not* the OOMWOO hardware design (which is built from
> sourced Roborock/Dreame/Xiaomi parts). This RFC eventually re-runs the same tests on real
> OOMWOO hardware.
> *Status — ready to start work.* The bring-up itself is doable now — connect the
> [placeholder Proscenic M6 Pro to ROS2](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/)
> with the [bridge](https://github.com/remakeai/vacuum_ros2_bridge). Re-running each behavior's
> acceptance tests follows as those behaviors land, but doing the bring-up / bridge now unblocks
> everyone else's hardware testing.
# Important References
- [Connect a robot vacuum (Proscenic M6 Pro) to ROS2 — tutorial](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/) — the primary how-to for getting a real vacuum onto ROS2.
- [remakeai/vacuum_ros2_bridge](https://github.com/remakeai/vacuum_ros2_bridge) — ROS2 bridge for a 3irobotix CRL-200-based vacuum (Proscenic), full ROS2 control.
- [codetiger/VacuumTiger](https://github.com/codetiger/VacuumTiger) — 3irobotix CRL-200-based low-level control, reverse engineered.
- The behavior RFCs whose tests you re-run on hardware: [clean-and-map](../clean-and-map), [nav-localize](../nav-localize), [dock-cycle](../dock-cycle), [recovery-safety](../recovery-safety), [floor-care](../floor-care), [cleaning-jobs](../cleaning-jobs).
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract that hardware bring-up should validate.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- connect a real vacuum to ROS2
- follow the [tutorial](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/) on a Proscenic M6 Pro or compatible 3irobotix CRL-200-based vacuum
- bring up the real sensors and actuators on ROS2: LiDAR, odometry, motors, bumper, battery, dock signals
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- match the simulation interfaces
- map the sim topics / actions to the real robot so the behavior packages run *unchanged* wherever possible; document every gap
- bring-up checklist: teleop, sensor sanity checks, e-stop verified before any autonomous run
- re-run the sim acceptance tests on hardware
- run each behavior's acceptance tests on the real robot: [clean-and-map](../clean-and-map), [nav-localize](../nav-localize), [dock-cycle](../dock-cycle), [recovery-safety](../recovery-safety), [floor-care](../floor-care) (as the hardware allows), [cleaning-jobs](../cleaning-jobs)
- record real-world results; document *sim-to-real gaps*, hardware-specific issues, and tuning changes
- be explicit about coverage
- if a behavior *can't* be tested on a given robot (e.g. no mop or no auto-empty dock), *log it* — do not silently skip it
- submit a PR (pull request) to `contributions/live-robot-bringup/<your-github-username>/`
- link to the bring-up / bridge package(s) and config
- instructions, documentation - exact hardware, how to connect, run, troubleshoot
- test results per behavior, videos, logs, and a sim-to-real notes write-up
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- A real vacuum is *connected to ROS2* with LiDAR, odometry, motors, bumper, battery, and dock signals working
- The sim interfaces are matched so behavior packages run on hardware with minimal changes; gaps are documented
- Each simulated behavior is *reproduced on the real robot* — or there is a *documented reason* it couldn't be (no silent skips)
- Real-world test results, videos, and a *sim-to-real gap* write-up are included
- Reproducible by someone else with the same hardware
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+71
View File
@@ -0,0 +1,71 @@
# Localization & Navigation on a Known Map (ROS2 package)
Once the [first clean](../clean-and-map) has produced a map, the robot must be able
to *operate on that saved map*: localize itself, navigate to any goal with Nav2,
recover its pose when it gets *lost* or is picked up and moved (the *kidnapped
robot* problem), and *resume an unfinished map*. Because the physical robot isn't
built yet, this is a *Gazebo simulation*; it is later re-validated on hardware in
the [live-robot-bringup RFC](../live-robot-bringup).
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
# Important References
- [clean-and-map RFC](../clean-and-map) — produces the saved/partial map this package consumes.
- [urdf-gazebo-sim RFC](../urdf-gazebo-sim) — robot URDF, Gazebo world(s), bumper.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- [Gazebo + Nav2 simulation tutorial](https://makerspet.com/blog/tutorial-map-navigate-ros2-robot-in-simulation/) — baseline diff-drive + LiDAR robot in a Gazebo world.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- Nav2 (navigation), AMCL (localization), and slam_toolbox (localization mode / serialized session continue) are the expected building blocks.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- reproduce the baseline first
- load a map saved by [clean-and-map](../clean-and-map) and bring up Nav2 + a localizer (AMCL or slam_toolbox localization mode) on the [urdf-gazebo-sim](../urdf-gazebo-sim) robot
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- localization on a known map
- *global initial localization at startup* without being given a pose (e.g. AMCL global init / scan-matching) — the robot figures out where it is on the saved map
- track pose reliably during navigation; expose a localization-confidence signal (covariance / scan-match score)
- navigation
- Nav2 navigate-to-pose and navigate-through-poses to arbitrary goals on the saved map
- obey dynamic obstacles via the local costmap
- lost / kidnapped recovery
- *detect* when localization confidence drops (low score, high covariance, or a detected pickup/kidnap)
- *relocalize*: rotate in place and/or drive to gather scans until the pose re-converges
- define a clear *"relocalized" success condition*, and what happens if it *fails* — hand off to the [dock-cycle](../dock-cycle) *find-the-dock-when-lost* fallback
- resume an unfinished map
- load a *partial / serialized SLAM session* (e.g. slam_toolbox serialization) and *continue mapping where it left off*, merging newly seen areas into the existing map without corrupting it
- test it well
- start from *many initial poses*, including a wrong or unknown initial pose
- *kidnap* the robot mid-run (teleport it in sim) and verify it recovers
- resume from several partial maps and verify the merged map is correct
- regression tests (headless, CI-friendly)
- relocalization success rate from random poses
- navigation success rate to random reachable goals
- map-resume correctness (resumed + continued map matches a from-scratch map of the same world)
- submit a PR (pull request) to `contributions/nav-localize/<your-github-username>/`
- link to ROS2 package(s)
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- videos of relocalization-when-lost and map-resume runs
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- On a saved map, from an *unknown initial pose*, the robot performs global localization and converges to the correct pose
- Nav2 navigation reaches arbitrary reachable goals reliably, avoiding dynamic obstacles
- When *lost / kidnapped*, the robot detects it, relocalizes, and resumes — or cleanly hands off to the find-the-dock fallback when relocalization fails
- An *unfinished map* can be loaded and mapping continued, producing a complete, uncorrupted map
- *Regression tests* pass and verify relocalization, navigation, and map-resume, runnable headless in CI
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
+345
View File
@@ -0,0 +1,345 @@
# Part Specs — Compiled Datasheets & Specifications
> **Contributor:** OsakaTX
> **Status:** Initial compilation — datasheets and specifications found via online research
> **Methodology:** Web research from manufacturer datasheets, public SDKs, open-source reverse-engineering projects, and Aliexpress listings
This document compiles the electrical and mechanical specifications found for key BOM
parts. Each section covers what was found and what is still missing.
---
## 1. Drive Wheel Assembly (Roborock S-family)
### Part Identification
The Roborock S4/S5/S5-Max/S6/S7-family drive wheel module is a complete unit:
gearmotor + incremental encoder + rubber tire + suspension + wheel-drop switch.
See BOM.md for the full Aliexpress search links.
### Motor Specs (Nidec 20N704RC70 — "Motor for Wheel")
Sourced from the [Nidec Robot Cleaner Motor catalog PDF](https://file.elecfans.com/web1/M00/CC/89/o4YBAF-ZOBKAQBvyADDMAglsvTw020.pdf):
| Parameter | Value |
|---|---|
| **Rated voltage** | 14.4 V |
| **No-load speed** | 16,800 rpm |
| **No-load current** | 0.4 A |
| **Rated speed** | 13,200 rpm |
| **Rated current** | 1.4 A |
| **Maximum power** | 13.8 W |
| **Noise (SPL)** | 45 dB(A) |
| **Status** | ⚠️ "In development" per Nidec catalog — model may have changed in production |
**Note:** The actual motor used in production Roborock wheels may differ from the
catalog entry above. The wheel module includes a **gearbox** (ratio not published)
that reduces this to the final wheel RPM. The encoder is mounted after the gearbox.
### Wheel Dimensions (from open PR #10 — alvarosamudio's URDF)
| Dimension | Value |
|---|---|
| **Wheel diameter** | 0.065 m (65 mm) |
| **Robot body diameter** | 0.33 m (330 mm) |
### Encoder
- **Type:** Incremental quadrature encoder (magnetic or optical, likely magnetic for dust resistance)
- **Known fact:** Connected to GD32F103 MCU timer inputs (quadrature decoder mode)
- **PPR:** ❔ **NOT FOUND** — needs physical disassembly and measurement or reverse-engineering
- The GD32F103VCT6 has hardware timers capable of quadrature decoding
- The 16-pin J25/J26 connectors carry encoder + cliff + bumper + misc signals
### Connector (Wheel Module to Main Board)
- **Connector type:** J25 (left wheel) / J26 (right wheel) — 16-pin, 1mm pitch SHD (shielded) connector
- **Known pins:** 2-4 pins for encoder A/B channels, 2 pins for wheel-drop sensor, remaining for other sensors
- **Full pinout:** ❔ **NOT CONFIRMED** — needs PCB tracing or oscilloscope probing
### Wheel-Drop Sensor
- **Model:** ❔ **NOT FOUND** — likely a microswitch or Hall-effect sensor within the wheel suspension assembly
- **Pinout:** ❔ **NOT CONFIRMED**
### What We Still Need
| Item | Status |
|---|---|
| Encoder type (magnetic vs optical) | ❔ Unknown |
| Encoder PPR (pulses per revolution) | ❔ Unknown — **critical for odometry** |
| Gearbox ratio | ❔ Unknown |
| Wheel module connector full pinout | ❔ Unknown |
| Wheel-drop sensor model + pinout | ❔ Unknown |
| Cable length | ❔ Unknown |
| Module weight | ❔ Unknown |
| Actual motor model in the production module | ❔ Needs teardown verification |
### References
- [Nidec Robot Cleaner Motor Product Introduction PDF](https://file.elecfans.com/web1/M00/CC/89/o4YBAF-ZOBKAQBvyADDMAglsvTw020.pdf)
- [codetiger/VacuumRobot — Mainboard Component Diagram](https://github.com/codetiger/VacuumRobot/blob/main/Research/Motherboard/Component_Diagram.md)
- [codetiger/VacuumRobot — Connection Evidence](https://github.com/codetiger/VacuumRobot/blob/main/Research/Motherboard/Connection_Evidence.md)
- [codetiger/VacuumTiger — Custom firmware implementing the protocol](https://github.com/codetiger/VacuumTiger)
---
## 2. Suction Fan / Blower
### Nidec 20N-series Blower Units (full assemblies)
From the [Nidec catalog PDF](https://file.elecfans.com/web1/M00/CC/89/o4YBAF-ZOBKAQBvyADDMAglsvTw020.pdf):
#### Standard Blower Units
| Parameter | 20N183L010 A | 20N704R310 / R500 | 20N704S310 | 22N709Q140 |
|---|---|---|---|---|
| Rated voltage | 12 V | 14.4 V | 14.4 V | 14.4 V |
| Rated speed | 15,000 rpm | 17,200 rpm | 15,100 rpm | 22,500 rpm |
| Rated current | 1.7 A | 2.2 A | 1.6 A | 4.5 A |
| Input power | 20 W | 31.7 W | 23 W | 64.8 W |
| Max static pressure | 1.8 kPa | 2.6 kPa | 3.0 kPa | 4.0 kPa |
| Max air volume | 0.63 m³/min | 0.78 m³/min | 0.63 m³/min | 0.99 m³/min |
| Noise | 67 dB(A) | 70 dB(A) | 68 dB(A) | 75 dB(A) |
| Fixing device | With | With | Without | Without |
#### Nidec BLDC Bare Motors (for Blower — without impeller housing)
| Parameter | 20N704K500 B | 20N704P110 A | 22N709P230 D | 35N048P010 |
|---|---|---|---|---|
| Rated voltage | 12 V | 13 V | 14.4 V | 18 V |
| No-load speed | 16,500 rpm | 23,000 rpm | 29,000 rpm | 28,800 rpm |
| No-load current | 0.4 A | 0.6 A | 0.6 A | 0.8 A |
| Rated speed | 13,500 rpm | 16,700 rpm | 24,800 rpm | 25,900 rpm |
| Rated current | 1.4 A | 1.75 A | 2.8 A | 7.1 A |
| Max power | 15.9 W | 21.2 W | 58.4 W | 74.6 W |
| Noise | 40 dB(A) | 50 dB(A) | 65 dB(A) | 60 dB(A) |
### Fan Control Interface (BLDC bare motors)
From the catalog, the Nidec Smart_20N series BLDC motors support:
| Feature | Details |
|---|---|
| **Control** | PWM input for speed control |
| **Feedback** | FG (frequency generator / tachometer) output |
| **Special** | 22N709P230 D additionally has SS (soft-start) |
| **Drive** | 3-phase BLDC — requires external driver/controller |
| **Voltage range** | 524 V (Smart_20N series datasheet) |
| **Mass** | 2530 g (bare motor) |
### Driving the Fans
- The open-source [ripinteer/fan_protector](https://github.com/ripinteer/fan_protector) project is a reference for driving/protecting these BLDC blowers
- The [jniebuhr/roborock-pcb](https://github.com/jniebuhr/roborock-pcb) project has a custom PCB for driving Roborock fans (Nidec-based), including connector pinout
- Connector on the Roborock CPAP board: JST XH 3-pin 2.54mm for motor, JST XH 2-pin 2.54mm for fan
### What We Still Need
| Item | Status |
|---|---|
| Full suction-assembly-level datasheet (including impeller housing) | ❔ Missing (we have bare motor specs) |
| Specific connector model + pinout for each assembly variant | ❔ Unknown |
| Cable length(s) | ❔ Unknown |
| Signal waveforms (PWM, FG) | ❔ Unknown |
| Weight of full assembly | ❔ Unknown |
### References
- [Nidec Robot Cleaner Motor PD](https://file.elecfans.com/web1/M00/CC/89/o4YBAF-ZOBKAQBvyADDMAglsvTw020.pdf)
- [Nidec Smart_20N series product page](https://www.nidec.com/en/product/search/category/B101/M102/S100/NCJ-20N-Type-3/)
- [jniebuhr/roborock-pcb — custom PCB with pinout](https://github.com/jniebuhr/roborock-pcb)
- [ripinteer/fan_protector — BLDC driver reference](https://github.com/ripinteer/fan_protector)
---
## 3. LiDAR — 3irobotix CRL-200S / Delta-2D
### Basic Specs
| Parameter | Value |
|---|---|
| **Model** | 3irobotix CRL-200S (same platform as Delta-2D) |
| **Type** | Laser triangulation (not ToF) |
| **Range** | 0.138 m @ 100% reflectivity |
| **Accuracy** | < 1% @ 5m |
| **Scan rate** | 410 Hz (configurable via motor voltage) |
| **Points/sec** | 25 KHz |
| **Wavelength** | 780 nm |
| **Laser class** | Class 1 |
| **Max ambient** | 1K lux |
| **Weight** | ~175 g |
| **Retail** | ~$2840 |
| **Life** | Estimated 1,500+ hours |
| **Power** | 0.35A idle, 0.37A ranging @ 5V (~1.75 W) + motor ~0.1A |
### Interface
| Parameter | Value |
|---|---|
| **Connector** | JST PH 2.0mm 5-pin |
| **Data** | UART 115200 baud, 8N1 |
| **Protocol** | Proprietary 3irobotix — 8-byte header + variable payload |
| **Commands** | 0xAE (health), 0xAD (measurement data) |
| **Distance multiplier** | 0.25 mm per LSB |
#### Pinout (J17 on 3irobotix CRL-200S mainboard)
| Pin | Function | Direction | Notes |
|---|---|---|---|
| 1 | Motor+ | Power | 5V for rotation |
| 2 | Motor- | Power | GND |
| 3 | TX | Output | LiDAR → MCU (UART RX) |
| 4 | RX | Input | MCU → LiDAR (UART TX) |
| 5 | GND | Power | Ground |
### Motor Control
| Parameter | Value |
|---|---|
| **Motor voltage range** | 2.23.8 V (for valid ranging) |
| **RPM range** | ~240420 RPM |
| **Motor drive** | Direct voltage control (external) or H-bridge / PWM |
| **Ranging fails below** | ~2.2 V motor (below 240 RPM) |
| **Ranging fails above** | ~4.0 V motor (above 460 RPM) |
| **Angular resolution** | 0.77° at 240 RPM → 1.41° at 420 RPM |
### Data Format (from notblackmagic.com reverse engineering)
Start byte `0xAA`, followed by:
- 2-byte packet type
- Data length
- Angle + distance pairs (each 2 bytes)
- Checksum byte
### References
- [kaiaai/awesome-2d-lidars — comparison table & pinout](https://github.com/kaiaai/awesome-2d-lidars)
- [kaiaai/LDS — Arduino LiDAR library supporting CRL-200S / Delta-2D](https://github.com/kaiaai/LDS)
- [notblackmagic.com — full reverse engineering of Delta-2G (same protocol)](https://notblackmagic.com/bitsnpieces/lidar-modules/)
- [codetiger/VacuumRobot — LiDAR protocol decoding](https://github.com/codetiger/VacuumRobot)
- [3irobotix Delta-2B SDK](https://github.com/CWRU-AutonomousVehiclesLab/Delta-2B-Lidar-SDK)
- Delta-1A protocol doc (shared with 2A/2B/2G/2D): see notblackmagic.com above
---
## 4. VL53L7CX — Multizone Time-of-Flight Sensor
### Basic Specs
| Parameter | Value |
|---|---|
| **Type** | Time-of-F Flight, 8×8 multizone (64 zones) |
| **Field of View** | 90° diagonal (65° × 65° typical) |
| **Range** | Up to 350 cm (varies by target reflectivity and ambient) |
| **I²C address** | 0x52 (default) — configurable |
| **I²C speed** | Up to 1 MHz (fast mode+) |
| **Supply** | 2.8 V (AVDD), 1.8 V (IOVDD) or 2.8 V (optional) |
| **Package** | 4.4 × 2.4 × 1.0 mm (LGA-12) |
### Pinout
| Pin | Name | Function |
|---|---|---|
| 1 | AVDD | Power (2.8 V) |
| 2 | GND | Ground |
| 3 | GPIO1 | Interrupt output (programmable) |
| 4 | LPn | Low power mode control |
| 5 | I2C_RST | I²C interface reset (active high) |
| 6 | SCL | I²C clock |
| 7 | SDA | I²C data |
| 8 | IOVDD | I/O voltage (1.8 V or 2.8 V) |
### Interface
- **I²C** up to 1 MHz
- Requires external pull-up resistors on SCL/SDA
- Interrupt pin (GPIO1) for data-ready signaling
- LPn pin for low-power mode control
### References
- [ST VL53L7CX Datasheet](https://www.st.com/resource/en/datasheet/vl53l7cx.pdf)
- [STM32duino VL53L7CX Arduino library](https://github.com/stm32duino/VL53L7CX)
- [UM3038 — User guide for using VL53L7CX](https://www.pololu.com/file/0J1993/um3038-a-guide-to-using-the-vl53l7cx-timeofflight-multizone-ranging-sensor-with-90-fov-stmicroelectronics.pdf)
---
## 5. IMU — MPU-6050 (common choice, architecture TBD)
### Basic Specs
| Parameter | Value |
|---|---|
| **Type** | 6-axis (3-axis gyro + 3-axis accelerometer) |
| **Gyro range** | ±250, ±500, ±1000, ±2000 °/s (programmable) |
| **Accel range** | ±2g, ±4g, ±8g, ±16g (programmable) |
| **I²C address** | 0x68 (AD0 low) or 0x69 (AD0 high) |
| **I²C speed** | Up to 400 kHz |
| **Supply** | 2.3753.46 V |
| **Package** | 4×4×0.9 mm QFN-24 |
### Pinout (QFN-24)
| Pin | Name | Function |
|---|---|---|
| 8 | SCL | I²C clock |
| 9 | SDA | I²C data |
| 12 | AD0 | I²C address select |
| 20 | INT | Interrupt output |
| 6 | VDD | Power (2.43.5 V) |
| 18 | VLOGIC | Logic reference (1.8V±5% or VDD) |
| 7, 10, 13, 19, 21 | GND | Ground |
### References
- [MPU-6000/6050 Register Map & Descriptions](https://cdn.sparkfun.com/datasheets/Sensors/Accelerometers/RM-MPU-6000A.pdf)
- [MPU-6050 Product Specification v3.4](https://www.cdiweb.com/datasheets/invensense/mpu-6050_datasheet_v3%204.pdf)
---
## 6. IR Cliff / Proximity Sensors — TCRT5000 (typical)
### Basic Specs
| Parameter | Value |
|---|---|
| **Type** | Reflective optical sensor (IR LED + phototransistor) |
| **Operating voltage** | LED: 1.21.5 V forward (typical), Phototransistor: up to 30 V |
| **Detection range** | 0.215 mm (for reliable cliff detection) |
| **Output** | Phototransistor collector (open collector — needs pull-up resistor) |
| **Package** | 4-pin DIP (standard leaded package) |
### Pinout
| Pin | Name | Function |
|---|---|---|
| 1 | A (Anode) | IR LED anode |
| 2 | C (Cathode) | IR LED cathode |
| 3 | C (Collector) | Phototransistor collector (output) |
| 4 | E (Emitter) | Phototransistor emitter (GND) |
### Typical Circuit
- IR LED: driven through a current-limiting resistor (e.g., 100220 Ω at 3.35 V)
- Phototransistor: collector pulled up to MCU voltage (3.3 V) via a 1047 kΩ resistor
- Output read as analog voltage (ADC) or digital threshold (comparator)
### References
- [TCRT5000 datasheet (components101)](https://components101.com/sensors/tcrt5000-ir-sensor-pinout-datasheet)
- [TCRT5000 guide (Utmel)](https://www.utmel.com/components/tcrt5000-ir-sensor-datasheet-pinout-and-circuit?id=697)
---
## Summary of Found vs Missing
| Part | Documentation Found | Critical Gaps |
|---|---|---|
| **Drive wheel** | Motor electrical specs (Nidec 20N704RC70), wheel diameter (65mm), connector type (16-pin SHD) | ❌ Encoder PPR, ❌ gearbox ratio, ❌ full connector pinout, ❌ wheel-drop sensor model |
| **Suction fan** | Complete Nidec catalog specs for many models, PWM/FG control, connector types (JST XH) | ❌ Impeller housing geometry, ❌ assembly weight, ❌ cable lengths |
| **LiDAR (CRL-200S)** | ✅ Full specs, pinout, protocol, RPM/voltage relationship, power consumption | Minor: exact motor model inside, formal manufacturer datasheet |
| **VL53L7CX ToF** | ✅ Full datasheet, I²C interface, pinout, FOV, range | None — standard ST component |
| **IMU (MPU-6050)** | ✅ Full datasheet, I²C interface, pinout | None — standard InvenSense component |
| **IR cliff sensor** | ✅ TCRT5000 specs, circuit, pinout | None — standard sensor, subject to final BOM choice |
| **Caster wheel** | ❌ Nothing found | Everything — model, dimensions, mounting, weight, any embedded sensor |
+99
View File
@@ -0,0 +1,99 @@
# Procure Part Specs & Datasheets for Sourced Components
We've already sourced candidate parts (see [BOM.md](../../BOM.md)). To design the I/O
board and firmware, *drive the motors and fans*, and build accurate mounts, we need each
part's detailed *electrical + mechanical specs* — pinouts, voltages, currents, encoder
PPR, torque, waveforms, etc. Vendors rarely publish these for vacuum sub-assemblies, so
contributors will *find datasheets, ask vendors, or safely reverse-engineer* them.
This is the *electrical/mechanical data* companion to
[source-3d-models](../source-3d-models) (which covers the *geometry*). No robotics
background needed — a multimeter, patience, and (for reverse-engineering) an oscilloscope go far.
Please check [BOM.md](../../BOM.md) and the [sourcing follow-along blog post](https://makerspet.com/blog/how-to-source-bom-for-oomwoo-open-source-vacuum-robot/)
for datasheets/specs already found.
## What we need, per part
### Drive wheel assembly (Roborock-family — see BOM)
- motor model; motor/assembly *datasheet* (if any)
- *encoder type + PPR* (pulses per revolution)
- *gearbox ratio*; *wheel diameter*
- rated + max motor *voltage*, *current* (no-load & stall), *torque*
- max / rated wheel *speed*
- *cable length(s)*; *connector models* (both ends); *full connector + motor pinouts*
- *wheel-drop sensor* model + pinout (these modules include one)
- signal *waveforms* (encoder channels, motor drive)
- assembly *weight*
### Suction fan / blower (several options sourced — see BOM)
- fan/motor model; *datasheet*
- rated *voltage, current, RPM*; *airflow* + *static pressure (Pa)*
- *how to drive it* — BLDC driver + control interface (PWM / tach / hall / 3-phase),
soft-start / protection behaviour
- *connector model(s) + pinout*; cable length
- signal *waveforms*
- *weight*
### Caster / universal wheel assembly (Roomba-family — see BOM)
- model, dimensions, mounting, any embedded sensor, weight, datasheet
## Already found vs still missing
*Found*
- *Fan datasheet (some options):* https://file.elecfans.com/web1/M00/CC/89/o4YBAF-ZOBKAQBvyADDMAglsvTw020.pdf
- *Nidec BLDC motors* (candidate fan motors — *bare motors, not the full suction assemblies*):
[NCJ-20N Type-3](https://www.nidec.com/en/product/search/category/B101/M102/S100/NCJ-20N-Type-3/),
[NCJ-20N Type-4](https://www.nidec.com/en/product/search/category/B101/M102/S100/NCJ-20N-Type-4/)
- *Driving the fans:* [ripinteer/fan_protector](https://github.com/ripinteer/fan_protector)
— reference for driving / protecting the BLDC blower (from earlier project research)
*Still missing (help wanted)*
- Connector *pinouts* + cable lengths for wheels, fans, caster
- *Encoder type + PPR*, gearbox ratios, torque/current under load
- *Wheel-drop sensor* model + pinout
- Signal *waveforms* (encoder, motor/fan drive)
- Suction-*assembly*-level datasheets (we have some bare motors, not the assemblies)
- Weights; caster specs
## Reverse-engineering — only if specs can't be found, and SAFELY
If a spec isn't published, reverse-engineer it by opening an existing vacuum and probing.
*Safety first:*
- Don't do it unless you're qualified, experienced.
- Opening a vacuum usually *voids the warranty* and *can damage it* — accept that risk knowingly.
- *Secure / prop up the vacuum* so it can't scoot off the table or bench when the wheels or
fan spin during testing (clamp it, or raise the wheels off the surface).
- Respect the *Li-ion battery* — don't short, pierce, or stress the pack; disconnect where sensible.
- Mind *pinch points and spinning parts* — keep fingers/hair clear of the impeller and brushes.
- Any *mains-connected* testing (e.g. a dock) — extra caution; isolate.
- Use a *multimeter + oscilloscope* to capture voltages, currents, and waveforms; trace and
*label connector pinouts*; photograph everything.
*Legal:*
- By performing any work for this project including reverse engineering you agree to
- wave liability, indemnify this project, the legal entity behind it (Remake AI Statutory Trust) and and its founder
- contribute your work and results thereof, if any, as open-source, to be published under Apache 2.0 license
## Submit
A PR to `contributions/part-specs/<your-github-username>/<part>/`:
- a spec sheet (markdown table) with everything you found
- any datasheets (PDF) and source links
- photos of connectors + labelled pinouts
- waveform captures where reverse-engineered
- provenance — which vendor / model / revision the data is from
- announce it in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
## Acceptance criteria
- Spec sheet for a part (or a clearly-scoped subset)
- Datasheets / links where found
- pinouts + waveforms where reverse-engineered when appropriate
- Provenance stated (part / vendor / revision)
- Verifiable by someone else with the same part
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple attempts
are welcome and useful even if not selected — a non-selected spec sheet is still a valid
reference and a fallback.
@@ -0,0 +1,28 @@
> ⚠️ **Tentative — not yet confirmed on a physical PCB.** The pinout, cable length, and connector
> below are educated guesses from photos and references. **Verify before wiring** — a wrong
> motor/encoder pinout can damage hardware. Corrections welcome (open a PR or issue).
## Drive Wheel pinout and cable length: ##
- Pinout:
- pin 1: Limit switch, grey
- pin 2: Limit switch, grey
- pin 3: Encoder 5v, orange
- pin 4: Encoder Signal, blue
- pin 5: Encoder Ground, brown
- pin 6: Motor power, black
- pin 7: Motor power, red
- Note: Some uncertainty around the encoder wires; I agreed with https://electronics.stackexchange.com/questions/549640/how-to-find-pinout-of-dc-geared-motor-with-encoder but do not have a pcb to hand to confirm. The comments there suggest the encoder is hall effect, but am not certain this is true.
- Cable length:
- 250mm ( Estimate Only - based on examining photos, please confirm with physical article )
- Connector:
- JST, might be XH but not certain.
Links:
https://electronics.stackexchange.com/questions/549640/how-to-find-pinout-of-dc-geared-motor-with-encoder
https://www.reddit.com/r/Roborock/comments/1t4akoj/new_wheel_for_roborock_s5v/
(used google translate to determine wire colour)
https://drive.google.com/file/d/1xLM9X-zjDowNAcrZBPqK-h6_eV-Fou7R/edit (disassembly of roborock vacuum; red and black wires clearly largest traces at 2:00, pin 1 marking also visible on connector)
## Evidence
![Roborock S4/S5 wheel module connector closeup](WM_V2_closeup.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

+71
View File
@@ -0,0 +1,71 @@
# Recovery Behaviors & Safety (ROS2 package)
Keep the robot *unstuck and safe*. This package provides a *recovery ladder*
(back up, wiggle, shake free, rotate, nudge, clear costmap, try an alternate path),
*escalation* when a recovery attempt fails, and a final *pause-and-alert* state
when everything fails. It also covers *safety*: cliff / wheel-drop / pickup
detection, an e-stop, and *status / error reporting* so a human knows what
happened. Because the physical robot isn't built yet, this is a *Gazebo
simulation*; it is later re-validated on hardware in the
[live-robot-bringup RFC](../live-robot-bringup).
> *Status — ready to start work.* No need to wait for OOMWOO hardware — develop it in the
> Gazebo sim ([urdf-gazebo-sim](../urdf-gazebo-sim)) or on the real
> [placeholder Proscenic M6 Pro](https://makerspet.com/blog/tutorial-connect-robot-vacuum-cleaner-to-ros-2-proscenic-m6-pro/).
> Say so in the [discussions](https://github.com/makerspet/oomwoo/discussions) so we can coordinate.
# Important References
- [clean-and-map RFC](../clean-and-map) and [urdf-gazebo-sim RFC](../urdf-gazebo-sim) — the *bumper* (left / right / front) and any cliff / wheel-drop sensors this package reacts to.
- [nav-localize RFC](../nav-localize) — pickup / kidnap detection ties into relocalization.
- [ROS2 software interfaces](../../docs/SOFTWARE_INTERFACES.md) — shared topic/action/service contract for simulation-first modules.
- [OOMWOO ROS2 development](https://github.com/makerspet/oomwoo-install) — build OOMWOO ROS2 Docker image(s) with your packages.
- Nav2's behavior/recovery server is a good starting point for composing recoveries.
- [Project discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- [Discord server](https://discord.gg/3y2JKz5T25)
# Request for Contribution - Instructions
- implement a *recovery ladder*
- a configurable, ordered set of behaviors, e.g. *back up*, *wiggle / shake free*, *rotate in place*, *clear the costmap*, *nudge*, *try an alternate path*
- pick the right recovery for the situation (wedged, bumper-jammed, no valid path, localization lost)
- post in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) to let everyone know you're working on it, and post your progress
- *escalation*
- if behavior *N* fails, try *N+1*; track attempts per situation so the robot doesn't repeat a recovery forever
- *pause-and-alert*
- when the whole ladder is exhausted, stop safely, publish a clear *error / status*, and wait for a human or a resume command — *never thrash*
- *safety sensors*
- detect *cliffs*, *wheel drop*, *pickup / kidnap* (hand pickup detection to [nav-localize](../nav-localize)), and bumper jams; respond by stopping motors / entering a safe state
- *e-stop*
- a software emergency stop that brings the robot to a safe state immediately
- *status & error reporting*
- a structured robot-state / error topic, human-readable, suitable for Home Assistant, so a person knows *why* the robot paused
- test it well
- induce wedged / stuck situations in sim: trap the robot, drop a dynamic obstacle onto it, place it at a cliff edge, lift it
- verify the ladder runs, escalates, then pauses-and-reports; verify it never thrashes indefinitely
- regression tests (headless, CI-friendly)
- recovery success rate across induced stuck scenarios
- guaranteed termination — the robot always reaches *recovered* or *paused-and-reported*, never an infinite loop
- safety responses fire on cliff / wheel-drop / pickup / e-stop
- submit a PR (pull request) to `contributions/recovery-safety/<your-github-username>/`
- link to ROS2 package(s)
- instructions, documentation - how to install, run, configure, troubleshoot, test results
- videos of recovery, escalation, and pause-and-alert
- announce your submission in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- iterate with review
- TBD, expect the RFC to evolve
## Acceptance criteria
Objective, measurable. Examples:
- A configurable *recovery ladder* runs the right behaviors for the situation and frees the robot in the large majority of induced stuck scenarios
- Recoveries *escalate* and are bounded — the robot never repeats a failed recovery forever
- When recovery is impossible, the robot *pauses safely and reports a clear error*, then resumes on command
- *Safety*: cliff, wheel-drop, pickup, and e-stop all bring the robot to a safe state
- *Status / error reporting* is structured and Home-Assistant-friendly
- *Regression tests* pass, including a guaranteed-termination check, runnable headless in CI
- Documented and reliably reproducible by someone else
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple
attempts are welcome and useful even if not selected — modules are swappable, and
a non-selected design is still a valid learning exercise and a fallback.
@@ -0,0 +1,117 @@
# Recovery Safety Prototype by xbattlax
This contribution provides a first ROS2 recovery-and-safety package for the
OOMWOO recovery-safety RFC. It is deliberately small, deterministic, and
simulation-first:
- a bounded recovery ladder for bumper, wedged, no-path, and lost-localization
situations
- immediate safe stop for e-stop, cliff, wheel-drop, and pickup events
- pause-and-alert when the ladder is exhausted
- a structured JSON status topic suitable for later Home Assistant integration
- unit tests for guaranteed termination and safety responses
The core ladder lives in pure Python, so it can be regression-tested without a
running ROS graph. The ROS2 node is a thin adapter around that core.
## Package
`oomwoo_recovery_safety`
Location:
```text
contributions/recovery-safety/xbattlax/oomwoo_recovery_safety
```
## Interfaces
### Subscribed topics
| Topic | Type | Purpose |
|---|---|---|
| `/bumper_left` | `ros_gz_interfaces/msg/Contacts` | Trigger left-bumper recovery after filtering ground-plane contacts. |
| `/bumper_right` | `ros_gz_interfaces/msg/Contacts` | Trigger right-bumper recovery after filtering ground-plane contacts. |
| `/oomwoo/recovery/event` | `std_msgs/msg/String` | Manual/test trigger. Payload examples: `wedged`, `no_valid_path`, `localization_lost`, `bumper_front`. |
| `/oomwoo/recovery/behavior_result` | `std_msgs/msg/String` | Optional external result for the current behavior: `succeeded`, `failed`, or JSON `{"outcome":"succeeded"}`. |
| `/oomwoo/safety/e_stop` | `std_msgs/msg/Bool` | Immediate non-recoverable pause. |
| `/oomwoo/safety/cliff` | `std_msgs/msg/Bool` | Immediate safety pause. |
| `/oomwoo/safety/wheel_drop` | `std_msgs/msg/Bool` | Immediate safety pause. |
| `/oomwoo/safety/pickup` | `std_msgs/msg/Bool` | Immediate safety pause for pickup/kidnap handoff. |
| `/oomwoo/recovery/reset` | `std_msgs/msg/Bool` | Reset a paused/recovered controller when true. |
### Published topics
| Topic | Type | Purpose |
|---|---|---|
| `/cmd_vel` | `geometry_msgs/msg/Twist` | Short bounded motion commands for recovery. |
| `/oomwoo/status` | `std_msgs/msg/String` | JSON status with `state`, `reason_code`, `message`, `recoverable`, `source`, and recovery metadata. |
| `/oomwoo/recovery/command` | `std_msgs/msg/String` | JSON command for non-motion actions such as `clear_costmap`. |
## Build
From the OOMWOO ROS2 container:
```bash
source /opt/ros/jazzy/setup.bash
cd /workspace
colcon build \
--base-paths contributions/recovery-safety/xbattlax/oomwoo_recovery_safety \
--packages-select oomwoo_recovery_safety
```
## Test
```bash
source /opt/ros/jazzy/setup.bash
cd /workspace
colcon test \
--base-paths contributions/recovery-safety/xbattlax/oomwoo_recovery_safety \
--packages-select oomwoo_recovery_safety
colcon test-result --verbose
```
The tests cover:
- bounded escalation to pause-and-alert
- successful recovery stopping the ladder
- e-stop and safety events entering safe pause
- ignored duplicate triggers while already recovering
- JSON status shape
## Run
```bash
source /opt/ros/jazzy/setup.bash
source install/setup.bash
ros2 launch oomwoo_recovery_safety recovery_safety.launch.py
```
Manual trigger example:
```bash
ros2 topic pub --once /oomwoo/recovery/event std_msgs/msg/String "{data: bumper_front}"
```
Mark the current behavior as successful:
```bash
ros2 topic pub --once /oomwoo/recovery/behavior_result std_msgs/msg/String "{data: succeeded}"
```
Trigger e-stop:
```bash
ros2 topic pub --once /oomwoo/safety/e_stop std_msgs/msg/Bool "{data: true}"
```
## Current limitations
- This is a first integration scaffold, not a full Nav2 behavior-server plugin.
- `clear_costmap` is published as an intent on `/oomwoo/recovery/command`; a
future adapter should call Nav2 costmap clear services directly.
- Success detection is external for now. If no `succeeded` result arrives before
a behavior's timeout, the node escalates to the next behavior and eventually
pauses.
- Cliff, wheel-drop, and pickup are represented as boolean topics until the
hardware/simulation message contract is finalized.
@@ -0,0 +1,15 @@
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription(
[
Node(
package="oomwoo_recovery_safety",
executable="recovery_safety_node",
name="recovery_safety",
output="screen",
)
]
)
@@ -0,0 +1 @@
"""OOMWOO recovery-safety prototype package."""
@@ -0,0 +1,271 @@
from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum
import json
from typing import Mapping
class Situation(str, Enum):
BUMPER_LEFT = "bumper_left"
BUMPER_RIGHT = "bumper_right"
BUMPER_FRONT = "bumper_front"
WEDGED = "wedged"
NO_VALID_PATH = "no_valid_path"
LOCALIZATION_LOST = "localization_lost"
CLIFF = "cliff"
WHEEL_DROP = "wheel_drop"
PICKUP = "pickup"
E_STOP = "e_stop"
class ControllerState(str, Enum):
IDLE = "idle"
RECOVERING = "recovering"
RECOVERED = "recovered"
PAUSED = "paused"
class DecisionKind(str, Enum):
START_STEP = "start_step"
STATUS_ONLY = "status_only"
IGNORED = "ignored"
@dataclass(frozen=True)
class RecoveryStep:
name: str
command: str
duration_sec: float
linear_x: float = 0.0
angular_z: float = 0.0
@dataclass(frozen=True)
class RecoveryStatus:
state: str
reason_code: str
message: str
recoverable: bool
source: str = "oomwoo_recovery_safety"
situation: str | None = None
behavior: str | None = None
step_index: int | None = None
ladder_length: int | None = None
def to_json(self) -> str:
return json.dumps(asdict(self), sort_keys=True)
@dataclass(frozen=True)
class Decision:
kind: DecisionKind
status: RecoveryStatus
step: RecoveryStep | None = None
SAFETY_SITUATIONS = {
Situation.CLIFF,
Situation.WHEEL_DROP,
Situation.PICKUP,
Situation.E_STOP,
}
DEFAULT_LADDERS: Mapping[Situation, tuple[RecoveryStep, ...]] = {
Situation.BUMPER_LEFT: (
RecoveryStep("back_up", "twist", 0.8, linear_x=-0.12),
RecoveryStep("rotate_away_from_left_bumper", "twist", 1.0, linear_x=-0.06, angular_z=-0.55),
RecoveryStep("wiggle_free", "twist", 0.7, linear_x=-0.04, angular_z=0.85),
RecoveryStep("clear_costmap", "clear_costmap", 0.1),
),
Situation.BUMPER_RIGHT: (
RecoveryStep("back_up", "twist", 0.8, linear_x=-0.12),
RecoveryStep("rotate_away_from_right_bumper", "twist", 1.0, linear_x=-0.06, angular_z=0.55),
RecoveryStep("wiggle_free", "twist", 0.7, linear_x=-0.04, angular_z=-0.85),
RecoveryStep("clear_costmap", "clear_costmap", 0.1),
),
Situation.BUMPER_FRONT: (
RecoveryStep("back_up", "twist", 0.9, linear_x=-0.14),
RecoveryStep("rotate_left", "twist", 0.8, angular_z=0.6),
RecoveryStep("rotate_right", "twist", 0.8, angular_z=-0.6),
RecoveryStep("clear_costmap", "clear_costmap", 0.1),
),
Situation.WEDGED: (
RecoveryStep("back_up", "twist", 1.0, linear_x=-0.12),
RecoveryStep("wiggle_left", "twist", 0.6, linear_x=-0.04, angular_z=0.9),
RecoveryStep("wiggle_right", "twist", 0.6, linear_x=-0.04, angular_z=-0.9),
RecoveryStep("rotate_in_place", "twist", 1.2, angular_z=0.7),
RecoveryStep("clear_costmap", "clear_costmap", 0.1),
),
Situation.NO_VALID_PATH: (
RecoveryStep("clear_costmap", "clear_costmap", 0.1),
RecoveryStep("nudge_reverse", "twist", 0.6, linear_x=-0.08),
RecoveryStep("rotate_in_place", "twist", 1.0, angular_z=0.6),
),
Situation.LOCALIZATION_LOST: (
RecoveryStep("stop_and_wait", "stop", 0.1),
RecoveryStep("rotate_to_collect_scans", "twist", 1.5, angular_z=0.45),
),
}
class RecoveryController:
def __init__(self, ladders: Mapping[Situation, tuple[RecoveryStep, ...]] | None = None):
self._ladders = dict(ladders or DEFAULT_LADDERS)
self._state = ControllerState.IDLE
self._situation: Situation | None = None
self._step_index = 0
self._current_step: RecoveryStep | None = None
self._last_status = self._make_status("READY", "Recovery controller ready", True)
@property
def state(self) -> ControllerState:
return self._state
@property
def last_status(self) -> RecoveryStatus:
return self._last_status
def trigger(self, situation: Situation | str) -> Decision:
parsed = self._parse_situation(situation)
if parsed in SAFETY_SITUATIONS:
return self._pause(
parsed,
reason_code=self._safety_reason(parsed),
message=f"Safety event {parsed.value} paused the robot",
recoverable=False,
)
if self._state == ControllerState.RECOVERING:
status = self._make_status(
"RECOVERY_ALREADY_ACTIVE",
f"Ignoring {parsed.value}; already recovering from {self._situation.value}",
True,
)
self._last_status = status
return Decision(DecisionKind.IGNORED, status)
if self._state == ControllerState.PAUSED:
status = self._make_status(
"RECOVERY_PAUSED",
f"Ignoring {parsed.value}; controller is paused and needs reset",
self._last_status.recoverable,
)
self._last_status = status
return Decision(DecisionKind.IGNORED, status)
ladder = self._ladders.get(parsed)
if not ladder:
return self._pause(
parsed,
reason_code="NO_RECOVERY_LADDER",
message=f"No recovery ladder configured for {parsed.value}",
recoverable=True,
)
self._state = ControllerState.RECOVERING
self._situation = parsed
self._step_index = 0
self._current_step = ladder[0]
status = self._make_status(
"RECOVERY_STARTED",
f"Starting recovery step {self._current_step.name}",
True,
)
self._last_status = status
return Decision(DecisionKind.START_STEP, status, self._current_step)
def step_succeeded(self) -> Decision:
if self._state != ControllerState.RECOVERING:
status = self._make_status("NO_ACTIVE_RECOVERY", "No active recovery to complete", True)
self._last_status = status
return Decision(DecisionKind.IGNORED, status)
self._state = ControllerState.RECOVERED
self._current_step = None
status = self._make_status("RECOVERED", "Recovery succeeded", True)
self._last_status = status
return Decision(DecisionKind.STATUS_ONLY, status)
def step_failed(self, detail: str = "step failed") -> Decision:
if self._state != ControllerState.RECOVERING or self._situation is None:
status = self._make_status("NO_ACTIVE_RECOVERY", "No active recovery to fail", True)
self._last_status = status
return Decision(DecisionKind.IGNORED, status)
ladder = self._ladders[self._situation]
next_index = self._step_index + 1
if next_index >= len(ladder):
return self._pause(
self._situation,
reason_code="RECOVERY_EXHAUSTED",
message=f"Recovery ladder exhausted after {detail}",
recoverable=True,
)
self._step_index = next_index
self._current_step = ladder[next_index]
status = self._make_status(
"RECOVERY_ESCALATED",
f"Escalating after {detail}; starting {self._current_step.name}",
True,
)
self._last_status = status
return Decision(DecisionKind.START_STEP, status, self._current_step)
def reset(self) -> Decision:
self._state = ControllerState.IDLE
self._situation = None
self._step_index = 0
self._current_step = None
status = self._make_status("READY", "Recovery controller reset", True)
self._last_status = status
return Decision(DecisionKind.STATUS_ONLY, status)
def _pause(
self,
situation: Situation,
*,
reason_code: str,
message: str,
recoverable: bool,
) -> Decision:
self._state = ControllerState.PAUSED
self._situation = situation
self._current_step = None
status = self._make_status(reason_code, message, recoverable)
self._last_status = status
return Decision(DecisionKind.STATUS_ONLY, status)
def _make_status(self, reason_code: str, message: str, recoverable: bool) -> RecoveryStatus:
ladder_length = None
if self._situation in self._ladders:
ladder_length = len(self._ladders[self._situation])
return RecoveryStatus(
state=self._state.value,
reason_code=reason_code,
message=message,
recoverable=recoverable,
situation=self._situation.value if self._situation else None,
behavior=self._current_step.name if self._current_step else None,
step_index=self._step_index if self._current_step else None,
ladder_length=ladder_length,
)
@staticmethod
def _parse_situation(situation: Situation | str) -> Situation:
if isinstance(situation, Situation):
return situation
try:
return Situation(str(situation).strip().lower())
except ValueError as exc:
raise ValueError(f"Unknown recovery situation: {situation}") from exc
@staticmethod
def _safety_reason(situation: Situation) -> str:
if situation == Situation.E_STOP:
return "E_STOP"
return f"SAFETY_{situation.value.upper()}"
@@ -0,0 +1,173 @@
from __future__ import annotations
import json
from time import monotonic
from geometry_msgs.msg import Twist
import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.exceptions import ROSInterruptException
from rclpy.node import Node
from ros_gz_interfaces.msg import Contacts
from std_msgs.msg import Bool, String
from oomwoo_recovery_safety.core import Decision, DecisionKind, RecoveryController, Situation
class RecoverySafetyNode(Node):
def __init__(self):
super().__init__("recovery_safety")
self._controller = RecoveryController()
self._active_deadline: float | None = None
self._cmd_pub = self.create_publisher(Twist, "cmd_vel", 10)
self._status_pub = self.create_publisher(String, "oomwoo/status", 10)
self._command_pub = self.create_publisher(String, "oomwoo/recovery/command", 10)
self.create_subscription(Contacts, "bumper_left", self._bumper_left_cb, 10)
self.create_subscription(Contacts, "bumper_right", self._bumper_right_cb, 10)
self.create_subscription(String, "oomwoo/recovery/event", self._event_cb, 10)
self.create_subscription(String, "oomwoo/recovery/behavior_result", self._behavior_result_cb, 10)
self.create_subscription(Bool, "oomwoo/safety/e_stop", self._e_stop_cb, 10)
self.create_subscription(Bool, "oomwoo/safety/cliff", self._cliff_cb, 10)
self.create_subscription(Bool, "oomwoo/safety/wheel_drop", self._wheel_drop_cb, 10)
self.create_subscription(Bool, "oomwoo/safety/pickup", self._pickup_cb, 10)
self.create_subscription(Bool, "oomwoo/recovery/reset", self._reset_cb, 10)
self.create_timer(0.05, self._timer_cb)
self._publish_status(self._controller.last_status)
def _bumper_left_cb(self, msg: Contacts):
if self._has_real_contact(msg):
self._execute(self._controller.trigger(Situation.BUMPER_LEFT))
def _bumper_right_cb(self, msg: Contacts):
if self._has_real_contact(msg):
self._execute(self._controller.trigger(Situation.BUMPER_RIGHT))
def _event_cb(self, msg: String):
try:
self._execute(self._controller.trigger(msg.data))
except ValueError as exc:
self.get_logger().warn(str(exc))
def _behavior_result_cb(self, msg: String):
outcome = self._parse_outcome(msg.data)
if outcome == "succeeded":
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.step_succeeded())
elif outcome == "failed":
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.step_failed("external failure result"))
else:
self.get_logger().warn(f"Ignoring unknown behavior outcome: {msg.data}")
def _e_stop_cb(self, msg: Bool):
if msg.data:
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.trigger(Situation.E_STOP))
def _cliff_cb(self, msg: Bool):
if msg.data:
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.trigger(Situation.CLIFF))
def _wheel_drop_cb(self, msg: Bool):
if msg.data:
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.trigger(Situation.WHEEL_DROP))
def _pickup_cb(self, msg: Bool):
if msg.data:
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.trigger(Situation.PICKUP))
def _reset_cb(self, msg: Bool):
if msg.data:
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.reset())
def _timer_cb(self):
if self._active_deadline is None or monotonic() < self._active_deadline:
return
self._stop_motion()
self._active_deadline = None
self._execute(self._controller.step_failed("behavior timeout"))
def _execute(self, decision: Decision):
self._publish_status(decision.status)
if decision.kind != DecisionKind.START_STEP or decision.step is None:
return
step = decision.step
if step.command == "twist":
twist = Twist()
twist.linear.x = step.linear_x
twist.angular.z = step.angular_z
self._cmd_pub.publish(twist)
elif step.command == "stop":
self._stop_motion()
else:
self._publish_command(step.command, step.name)
self._active_deadline = monotonic() + step.duration_sec
def _publish_status(self, status):
self._status_pub.publish(String(data=status.to_json()))
def _publish_command(self, command: str, behavior: str):
payload = {
"command": command,
"behavior": behavior,
"source": "oomwoo_recovery_safety",
}
self._command_pub.publish(String(data=json.dumps(payload, sort_keys=True)))
def _stop_motion(self):
self._cmd_pub.publish(Twist())
@staticmethod
def _has_real_contact(msg: Contacts) -> bool:
for contact in msg.contacts:
names = {contact.collision1.name, contact.collision2.name}
if not any("ground_plane" in name.split("::") for name in names):
return True
return False
@staticmethod
def _parse_outcome(raw: str) -> str:
value = raw.strip().lower()
if value.startswith("{"):
try:
value = str(json.loads(raw).get("outcome", "")).strip().lower()
except json.JSONDecodeError:
return ""
return value
def main(args=None):
rclpy.init(args=args)
node = RecoverySafetyNode()
try:
rclpy.spin(node)
except (KeyboardInterrupt, ExternalShutdownException, ROSInterruptException):
pass
except Exception as exc:
if "context is not valid" not in str(exc):
raise
finally:
node.destroy_node()
try:
if rclpy.ok():
rclpy.shutdown()
except Exception as exc:
if "rcl_shutdown already called" not in str(exc):
raise
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>oomwoo_recovery_safety</name>
<version>0.1.0</version>
<description>Bounded recovery ladder and safety pause node for OOMWOO.</description>
<maintainer email="xbattlax@gmail.com">xbattlax</maintainer>
<license>Apache-2.0</license>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>rclpy</exec_depend>
<exec_depend>ros_gz_interfaces</exec_depend>
<exec_depend>std_msgs</exec_depend>
<test_depend>pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
@@ -0,0 +1 @@
oomwoo_recovery_safety
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/oomwoo_recovery_safety
[install]
install_scripts=$base/lib/oomwoo_recovery_safety
@@ -0,0 +1,27 @@
from setuptools import find_packages, setup
package_name = "oomwoo_recovery_safety"
setup(
name=package_name,
version="0.1.0",
packages=find_packages(exclude=["test"]),
data_files=[
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
("share/" + package_name, ["package.xml"]),
("share/" + package_name + "/launch", ["launch/recovery_safety.launch.py"]),
],
install_requires=["setuptools"],
zip_safe=True,
maintainer="xbattlax",
maintainer_email="xbattlax@gmail.com",
description="Bounded recovery ladder and safety pause node for OOMWOO.",
license="Apache-2.0",
tests_require=["pytest"],
entry_points={
"console_scripts": [
"recovery_safety_node = oomwoo_recovery_safety.recovery_node:main",
],
},
)
@@ -0,0 +1,109 @@
import json
import pytest
from oomwoo_recovery_safety.core import ControllerState, DecisionKind, RecoveryController, Situation
def test_bumper_recovery_escalates_and_terminates():
controller = RecoveryController()
decision = controller.trigger(Situation.BUMPER_LEFT)
assert decision.kind == DecisionKind.START_STEP
assert decision.step.name == "back_up"
seen = [decision.step.name]
for _ in range(10):
decision = controller.step_failed("test failure")
if decision.kind == DecisionKind.START_STEP:
seen.append(decision.step.name)
continue
break
assert seen == [
"back_up",
"rotate_away_from_left_bumper",
"wiggle_free",
"clear_costmap",
]
assert controller.state == ControllerState.PAUSED
assert controller.last_status.reason_code == "RECOVERY_EXHAUSTED"
assert controller.last_status.recoverable is True
def test_success_stops_ladder():
controller = RecoveryController()
controller.trigger("bumper_front")
decision = controller.step_succeeded()
assert decision.kind == DecisionKind.STATUS_ONLY
assert controller.state == ControllerState.RECOVERED
assert controller.last_status.reason_code == "RECOVERED"
@pytest.mark.parametrize(
("situation", "reason"),
[
(Situation.E_STOP, "E_STOP"),
(Situation.CLIFF, "SAFETY_CLIFF"),
(Situation.WHEEL_DROP, "SAFETY_WHEEL_DROP"),
(Situation.PICKUP, "SAFETY_PICKUP"),
],
)
def test_safety_events_pause_immediately(situation, reason):
controller = RecoveryController()
decision = controller.trigger(situation)
assert decision.kind == DecisionKind.STATUS_ONLY
assert controller.state == ControllerState.PAUSED
assert controller.last_status.reason_code == reason
assert controller.last_status.recoverable is False
def test_duplicate_trigger_is_ignored_while_recovering():
controller = RecoveryController()
controller.trigger(Situation.BUMPER_RIGHT)
decision = controller.trigger(Situation.WEDGED)
assert decision.kind == DecisionKind.IGNORED
assert controller.state == ControllerState.RECOVERING
assert controller.last_status.reason_code == "RECOVERY_ALREADY_ACTIVE"
def test_reset_returns_to_idle_after_pause():
controller = RecoveryController()
controller.trigger(Situation.E_STOP)
decision = controller.reset()
assert decision.kind == DecisionKind.STATUS_ONLY
assert controller.state == ControllerState.IDLE
assert controller.last_status.reason_code == "READY"
def test_paused_controller_ignores_new_recovery_until_reset():
controller = RecoveryController()
controller.trigger(Situation.E_STOP)
decision = controller.trigger(Situation.BUMPER_LEFT)
assert decision.kind == DecisionKind.IGNORED
assert controller.state == ControllerState.PAUSED
assert controller.last_status.reason_code == "RECOVERY_PAUSED"
def test_status_json_shape():
controller = RecoveryController()
controller.trigger(Situation.NO_VALID_PATH)
payload = json.loads(controller.last_status.to_json())
assert payload["state"] == "recovering"
assert payload["reason_code"] == "RECOVERY_STARTED"
assert payload["recoverable"] is True
assert payload["source"] == "oomwoo_recovery_safety"
assert payload["situation"] == "no_valid_path"
assert payload["behavior"] == "clear_costmap"
+72
View File
@@ -0,0 +1,72 @@
# Source 3D Models (STEP) for Off-the-Shelf BOM Parts (procurement)
To design printed mounts and the chassis that *fit real sourced parts*, we need accurate
3D models (*STEP*) of the off-the-shelf components in the [BOM](../../BOM.md) — the
drive-wheel assembly, the suction fan/blower (several candidate models), the caster wheel,
the side-brush motor, and more. Manufacturers rarely publish these, so the community needs
to *obtain, measure, and model* them. Every printed mount and the chassis references this
geometry — see [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) §5.2 (mechanical interface
standard). Missing models block or force guesswork on the mechanical modules.
This is a great *first contribution* for anyone with CAD skills and calipers — no
robotics or programming needed, and the modules can be modeled *in parallel*.
## Priority parts (from the [BOM](../../BOM.md))
Start with the "ready now" mechanical parts:
- *Drive wheel assembly pair* (e.g. Roborock S5/S50/S55/S6/S7-family modules)
- *Caster / universal wheel* (e.g. Roomba i/j/e/500900 caster)
- *Suction fan / blower* — model the option(s) we shortlist (e.g. Dreame L10s / Nidec /
Roborock S8-family blowers listed in the BOM)
- *Side-brush motor*, *main-brush assembly*, *water pump*, *battery pack*, etc.
See [BOM.md](../../BOM.md) for the full list, the exact candidate models, and AliExpress
search links, plus [docs/ali_express_research.md](../../docs/ali_express_research.md) for
sourcing notes.
# Request for Contribution - Instructions
- *Pick a part* from the [BOM](../../BOM.md) and *claim it* in
[Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) or
[Discord](https://discord.gg/3y2JKz5T25) so two people don't model the same thing. Post progress too.
- *Get an accurate model* — two paths:
1. *Find an existing STEP* (manufacturer site, GrabCAD, etc.) and *verify it against the
real part* (dimensions match). Fastest — but confirm it's the same model/revision.
2. *Model it yourself*: order the exact part (AliExpress links are in the BOM), measure
with calipers, optionally use a 3D scanner and build an accurate STEP.
- *Capture the interface-relevant geometry* (external only — internal detail is not needed):
- Overall *bounding envelope* (so it fits the chassis space / height budget)
- *Mounting features*: holes, bosses, clips, bolt pattern — and their exact positions
- *Functional interfaces*: axle/shaft position + axis, wheel contact plane, fan air
inlet/outlet, connector / wire-exit locations
- *Mating faces* where other modules attach
- Note the *key overall dimensions*
- *Record provenance* — the exact vendor + link + which model/revision you measured. Parts
vary between sellers and revisions; say which one this model represents.
- *Submit a PR* to `contributions/source-3d-models/<your-github-username>/<part-name>/`:
- the *STEP* file (primary deliverable) + native CAD source (Fusion/SolidWorks/etc.) if you modeled it
- a *photo* of the real part (ideally next to your model / with calipers)
- the *source link* (AliExpress/vendor) and *key dimensions*
- notes on tolerances and how you measured
- announce it in [Project Discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
- Iterate with review.
- TBD, expect the RFC to evolve.
(*) If you have a strong reason to model a different variant than the BOM lists, post your
rationale in [discussions](https://github.com/makerspet/oomwoo/discussions?discussions_q=) first.
## Acceptance criteria
Objective, measurable. Examples:
- Accurate *STEP* of the specified part — bounding envelope, mounting features, and
functional interfaces match the measured real part (within a stated tolerance)
- Includes the *exact source link*, *key dimensions*, and a *photo* of the real part
- Please make sure the model is licensed as open-source
- *Verifiable/reproducible* by someone else who buys the same part
- STEP provided (+ native CAD source if self-modeled)
- Documented well enough that a mount designer can build against it with confidence
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple attempts
are welcome and useful even if not selected — models are swappable, and a non-selected
model is still a valid reference and a fallback.
+7
View File
@@ -0,0 +1,7 @@
# oomwoo URDF + Gazebo Simulation
Self-hosted package: [github.com/alvarosamudio/oomwoo_gazebo](https://github.com/alvarosamudio/oomwoo_gazebo)
ROS2 package with URDF model, Gazebo simulation worlds, bump recovery node, and Nav2/SLAM configuration for the OOMWOO robot vacuum. Built against the [ROS2 Software Interfaces](../../docs/SOFTWARE_INTERFACES.md) contract.
See the repo above for full documentation, setup instructions, and usage.
+49
View File
@@ -0,0 +1,49 @@
# Blower Fan Assembly (mechanical module)
> *On hold.* Fan options are *already sourced* — see the suction-fan rows in
> [BOM.md](../../BOM.md) (multiple Pa/price options: Dreame, Nidec, Roborock-family blowers).
> The project has also moved *away from the old teardown reference vacuum* (its parts are
> hard to source vs. big names like Roborock/Dreame/Xiaomi). This module resumes once enough
> components are sourced and a *3D reference design with the key parts* is sketched, so the
> housing can be designed to fit real parts.
>
> *Useful upstream work now:* model the sourced fans in
> [source-3d-models](../source-3d-models), and gather fan specs / how to drive them in
> [part-specs](../part-specs). Those unblock this design.
The vacuum fan assembly provides air suction to the main brush and streams air into the dust bin.
It consists of:
- a high-speed *BLDC blower motor + impeller* (already sourced — see BOM)
- a blower housing (volute) — to be designed
- a blower exhaust gasket
- blower housing rubber mounts
> *Design research note:* real-world cleaning does *not* track raw suction (Pa) —
> mid-range sealed motors match flagships. Prioritise a *well-sealed* airflow path
> (no leaks at the bin / fan / brush seams) and a good brush over chasing maximum Pa.
> See [design research](../../README.md#design-research).
# Request for Contribution — Instructions (resumes when off hold)
When this reopens, the task is to design a *3D-printable volute housing + gasket around a
sourced BLDC blower* (chosen from the [BOM](../../BOM.md)) that mates cleanly with the dust
bin and the chassis. Design goals:
- 3D-printable (assume PETG); ideally no supports; reliably reproducible
- sliced part fits ~20 × 25 cm, 20 cm height (split if needed)
- *well-sealed* airflow path (no leaks); mates with the dust bin + chassis
- low vibration; secure motor mounting
- gasket: find an off-the-shelf one, else print TPU
- submit STEP + native CAD + 3MF/STL + a sub-component BoM + docs/photos/video to
`contributions/vacuum-fan/<your-github-username>/`
## Acceptance criteria (when it resumes)
- Fits the (forthcoming) chassis + dust-bin interfaces without forcing changes to other modules
- Well-sealed, reasonably quiet, low-vibration, strong suction with the chosen sourced blower
- 3D-printable and reliably reproducible by someone else
- Documented; STEP + native CAD source provided
- TBD, expect criteria to evolve
The maintainer selects among compliant candidates using these criteria. Multiple attempts
are welcome and useful even if not selected — modules are swappable, and a non-selected
design is still a valid learning exercise and a fallback.
+236
View File
@@ -0,0 +1,236 @@
# OOMWOO Architecture Brief
> *Status: DRAFT / skeleton.* This document defines the system so that modules
> can be built in parallel without colliding. Sections marked *TBD* are the
> gating decisions; until they are filled in, hardware modules that must fit
> together cannot be finalized. Treat the interface specs as the contract every
> module agrees to.
## 1. Purpose/Goal and scope
OOMWOO is an open-source, 3D-printed, ROS2-based home robot vacuum with 2D LiDAR
and Home Assistant support. It is designed to be *built from scratch by
the community*, module by module, clean well and to double as an affordable ROS2
development and learning platform.
*North star (not MVP):* OOMWOO is also the reference hardware for a broader
robot application platform. Architectural boundaries (especially the app layer in
§6) are drawn with that future in mind, but the MVP below deliberately excludes it.
## 2. Design principles
- *Open and swappable.* Every module has a defined interface. Any compliant
implementation can replace another. No module depends on the internals of
another, only on its published interface.
- *Simulation-first.* Software must run in Gazebo before it runs on hardware,
so contributors with no robot can still build and test.
- *Affordable and printable.* Target off-the-shelf parts (a CM4/CM5-class compute
module, common vacuum LiDARs, sourced Roborock/Dreame/Xiaomi motors and wear
parts) and FDM-printable chassis parts.
- *Safety is reviewed, not crowd-trusted.* Battery, charging, and motor-driver
modules pass a maintainer safety review before merge (see §8).
- *Reference-design backed.* A known-working vacuum (see the references in the
[README](../README.md)) anchors the geometry and proves feasibility.
## 3. System overview
```
LiDAR (UART, ~5 Hz) · MIPI camera(s) · IMU · serial audio
|
+-------------v-------------------------------+
| CPU - CM4 / CM5 (or pin-compatible module) |
| ROS2 · SLAM (slam_toolbox) · Nav2 · behavior |
| educational variant: ESP32-S3 + micro-ROS |
| (SLAM offboard on a dev PC over Wi-Fi) |
+-------------^----------------+---------------+
serial (cmds/telemetry) + | custom serial protocol
CPU-reset / health GPIO | (NOT micro-ROS)
+-------------+----------------v---------------+
| MCU - STM32G070 (FreeRTOS, static alloc) |
| motors · encoders · sensors · charging ctrl |
| SAFETY (no Linux/ROS2): bumper/cliff/wheel- |
| drop stop · current limit · CPU watchdog |
+-------------+--------------------+-----------+
| |
+----------+----+ +--------+---------+
| L/R drive | | suction fan, |
| wheels, brush | | bumper, cliff, |
| | | IR, wheel-drop |
+---------------+ +------------------+
Power: off-the-shelf 4S2P Li-ion pack (built-in BMS).
The CPU module + MCU sit on one carrier I/O board.
```
> The CPU/MCU split keeps *all hard safety on the MCU*, independent of Linux/ROS2.
> Interfaces are largely decided (see §5); refine as the io-pcb spec settles.
## 4. Coordinate frames and conventions
- *TBD:* Define `base_link` origin and orientation (REP-103: x-forward,
y-left, z-up). All mechanical mounting points and URDF frames reference this.
- *TBD:* Define the reference plane (floor contact), robot diameter, and height
envelope. *These two numbers gate every hardware module.* Source these from the *sourced
parts* + a 3D-scanned donor (see [source-3d-models](../contributions/source-3d-models));
the current baseline is a ~349 mm round body ([oomwoo-one URDF](https://github.com/makerspet/oomwoo-one)).
- Units: millimeters, kilograms, SI. Right-handed frames. Angles in radians.
## 5. Hardware architecture
### 5.1 Chassis and reference frame
The chassis is the *integration backbone*. It publishes the mounting interface
every other hardware module targets. Reference geometry (dimensions, wheelbase,
motor specs, mass) comes from the *sourced parts* ([BOM](../BOM.md)) + 3D scans of
a donor vacuum (see [source-3d-models](../contributions/source-3d-models)); the
[oomwoo-one URDF](https://github.com/makerspet/oomwoo-one) carries the current
~349 mm round-body baseline.
- *TBD:* Overall diameter and height budget.
- *TBD:* Mounting grid / bolt pattern standard (e.g., M3 on a defined pitch).
- *TBD:* Mass budget per module and total target mass.
### 5.2 Mechanical interface standard (the contract)
Every hardware module's RFC must specify, against this standard:
- Mounting points (bolt pattern, location relative to `base_link`).
- Bounding envelope (max size the module may occupy).
- Mass budget.
- Mating tolerances and print orientation.
- *TBD:* Define the standard connector/fastener set (screw sizes, heat-set
inserts, etc.) so parts from different authors actually mate.
### 5.3 Electrical interface standard (the contract)
- *Battery:* off-the-shelf pack with a *built-in BMS**4S2P Li-ion*, ~14.4 V
nominal, ~5200 mAh / ~75 Wh (OEM BRR-2P4S-5200 class), charged 16.8 V CC/CV with
NTC temperature sense. Chemistry is now decided; see the [BOM](../BOM.md).
- *TBD:* Power rails distributed to modules (VBAT, 5V, 3.3V) and connector types/pinouts.
- *CPU ↔ MCU:* a *custom high-speed serial protocol* (not micro-ROS) carries
commands/telemetry, plus discrete GPIOs (CPU power on/off, and a CPU-reset line
the MCU asserts on missed health packets). The *MCU owns motors and sensors*; the
CPU never drives them directly.
- *Sensors:* bumper/cliff/wheel-drop and analog IR are *MCU-side* (digital in / ADC);
the *LiDAR (UART, ~5 Hz)*, MIPI camera(s), IMU, and serial audio attach to the *CPU*.
### 5.4 Compute (CPU) and real-time controller (MCU)
OOMWOO splits compute across two processors — mirroring how consumer vacuums are
built, and, crucially, so that *safety never depends on Linux/ROS2*.
*CPU (compute module).* The I/O board is a *carrier* that accepts a *Raspberry Pi
Compute Module 4 or 5* and — because the CM4 pinout is a de-facto standard — the
many *pin-compatible alternative modules* (Radxa CM3/CM4, Pine64 SOQuartz, LuckFox
Core3566, …), several with an *NPU* for future on-device vision. The CPU runs
*ROS2, SLAM (slam_toolbox), Nav2, LiDAR processing, and high-level behavior*. CM
modules are low-profile (helps the height budget) and swappable (hackable, cheaper,
NPU options).
- *Minimum target: a 4 GB CM4/CM5 (or Pi 4).* Realistic prior art runs
slam_toolbox + Nav2 onboard a 4 GB Pi 4. Getting the floor to *2 GB* is a goal
(ROS2 composable nodes; selectively rewriting heavy Python nodes in Rust/C++) —
*no guarantee*, to be settled by the compute-benchmark. No 816 GB module needed.
- *Cooling:* no dedicated CPU fan — the suction fan's airflow cools the compute
board, as in consumer vacuums.
- The earlier bespoke *RK3562* reference schematic is *dropped* in favour of the
CM4/CM5 carrier.
*MCU (real-time / safety controller).* A dedicated microcontroller — *tentatively
the STM32G070RBT6* (~56 GPIO incl. 16 ADC channels, ~$1 at JLCPCB, LQFP not BGA) —
owns *motors, encoders, all sensors, battery-charging control, and safety*. Its
role is *fixed*: functionality does not migrate onto the CPU, and CPU work does not
migrate onto the MCU.
- Firmware is *tentatively FreeRTOS* (static allocation, watchdog, guaranteed
reaction times, CE-oriented) speaking a *custom serial protocol — not micro-ROS*
(the tried-and-true consumer-vacuum approach; cf. the reverse-engineered
[3irobotix protocol](https://github.com/codetiger/VacuumRobot)).
- *Hard safety lives here, independent of Linux/ROS2:* the MCU stops all motors on
a *bumper hit, cliff detection, or wheel-drop*, current-limits a *stuck brush*,
and *watchdogs the CPU* — if the CPU's health packets stop, it stops the motors
and can *reset the CPU*.
- Tentative ~60-signal pin budget: see the [io-pcb RFC](../contributions/io-pcb)
appendix (why the MCU needs a high-GPIO part).
*CPU ↔ MCU link.* A *high-speed serial* channel carries commands/telemetry both
ways, plus discrete GPIOs — notably the *CPU-reset* line the MCU asserts on missed
health packets, and CPU power on/off. Any LiDAR supported by `kaiaai/LDS` /
`lds2d` is interface-compatible.
### 5.5 Two build profiles
The same carrier I/O board + MCU supports two swappable compute configurations:
| | *Consumer / regular* | *Educational / lower-cost* |
|---|---|---|
| CPU-slot module | CM4 / CM5 (or pin-compatible alt) | *ESP32-S3* board in the CM4 form factor |
| Where ROS2 / SLAM runs | *onboard* (ROS2 + slam_toolbox + Nav2) | *offboard* on a local dev PC; ESP32-S3 runs *micro-ROS* |
| Link | self-contained robot | robot ↔ dev PC over *Wi-Fi* |
| Trade-off | plug-and-play for non-experts | cheaper, but Wi-Fi congestion / dead-zones — a learning platform, not a polished consumer product |
Onboard SLAM is the default for the consumer version *so non-experts can build and
use it* without setting up a separate ROS2 dev machine. The ESP32-S3 can only be
the *CPU-slot* option — it lacks the ~60 GPIO the MCU role needs, so it never
replaces the STM32.
## 6. Software architecture
### 6.1 ROS2 graph (MVP)
- Core nodes (MVP): LiDAR driver, base controller (diff-drive), odometry,
teleop, SLAM (manual mapping), TF/URDF publisher.
- *Interface contract:* each software module's RFC declares the ROS2 topics,
services, message types, and parameters it publishes/consumes. Modules depend
on these interfaces, not on each other's code. See
[SOFTWARE_INTERFACES.md](SOFTWARE_INTERFACES.md) for the current draft ROS2
graph contract.
### 6.2 Simulation
- Gazebo + URDF, with a set of residential-layout worlds for navigation and
coverage testing. Sim parity is a first-class requirement, not an afterthought.
### 6.3 Application layer (Phase 2 — north star, NOT in MVP)
A ROS2-agnostic layer that runs third-party apps locally in isolated *Podman*
containers, so app developers need no ROS2 expertise. Documented here only to
keep its boundary clean; *explicitly out of scope for the Aug 31 MVP.*
## 7. MVP definition (target: 2026-08-31)
*In scope:* ROS2 on a CM4/CM5-class compute module · LiDAR · manual SLAM/mapping · teleop drive ·
3D-printed chassis · Gazebo sim with URDF · evaluation + demo video. No dock,
no autonomous exploration, no Home Assistant, no app layer.
*Explicit non-goals for MVP:* autonomous coverage, docking, auto-empty, mopping,
Home Assistant, the app platform, accessories. These are later phases.
*Critical-path ownership:* the maintainer (+ small core) own the chassis,
interface specs, and integration so the MVP does not depend on volunteer delivery
timing. Community modules accelerate and improve the MVP; they do not block it.
## 8. Safety review gate
Battery, charging, motor-driver, and mains-adjacent modules require maintainer
safety review before merge. RFCs for these modules must include a hazard note
(over-current, thermal, short, mechanical pinch).
The battery risk is *reduced* by using an *off-the-shelf 4S2P Li-ion pack with a
built-in BMS* (over-charge / over-discharge / short protection); the review then
focuses on the *16.8 V CC/CV charging path + NTC temperature sense*. *Hard safety
lives on the MCU, never on Linux/ROS2* — it independently stops motors on
bumper/cliff/wheel-drop, current-limits a stuck brush, and watchdog-resets the CPU.
## 9. Roadmap (phases after MVP)
1. Rechargeable battery + basic dock + autonomous floor mapping.
2. Home Assistant integration.
3. Application layer (Podman app runtime) + first delightful apps.
4. Accessories and novel apps; integrations (e.g., LeRobot arm).
## 10. Open questions
- *Resolved:* the MCU runs a *custom serial protocol (not micro-ROS)*; the CPU runs
*onboard ROS2/SLAM/Nav2*. micro-ROS is used only in the *educational* ESP32-S3
profile (SLAM offboard on a dev PC). See §5.45.5.
- *Resolved:* battery is an off-the-shelf *4S2P Li-ion pack with a built-in BMS*,
charged 16.8 V CC/CV. See §5.3, §8.
- Can OOMWOO's onboard ROS2 stack fit in *2 GB* (composable nodes, selective Rust)
rather than 4 GB? To be answered by the compute-benchmark.
- MCU family: *STM32G070RBT6* is the tentative pick (GPIO/ADC count, ~$1 at JLCPCB,
LQFP) — open to alternatives.
- One hardware-agnostic HAL covering reference vacuum + DIY builds (community idea)?
- Module selection process: who decides which competing implementation wins, and
on what criteria? (See each module's acceptance criteria.)
+103
View File
@@ -0,0 +1,103 @@
# Contributing to OOMWOO
Thanks for your interest. OOMWOO is an open-source robot vacuum you build
yourself, and it's at a very early stage. That's the best time to get involved,
the foundations are still being laid and your input can shape the direction.
The project is built *module by module* so many people can work in parallel.
Browse the [module list in the README](../README.md#requests-for-contributions),
and see [ARCHITECTURE.md](ARCHITECTURE.md) for how the pieces fit together.
## Ways to help right now
You don't need to be a roboticist to contribute:
- *Ideas and feedback* — open a [Discussion](https://github.com/makerspet/oomwoo/discussions)
about features, design choices, or what would make you build one.
- *Code* — firmware, ROS2 packages, Home Assistant integration.
- *Hardware* — 3D-printable chassis design, mechanical parts, PCB.
- *Documentation* — build guides, wiring diagrams, troubleshooting notes.
- *Testing* — once there's something to build, real-world build reports are gold.
- *Spread the word* — star the repo, share your build, post a demo.
## Getting started
1. *Pick a module.* Browse the
[module list in the README](../README.md#requests-for-contributions) and choose a
hardware or software module. Software and simulation modules can start
immediately; hardware modules wait on the interface specs in
[ARCHITECTURE.md](ARCHITECTURE.md). Read the module's `README.md` so you know
the contract.
2. *Start a conversation first.* Claim or ask about the module in its
[Issue](https://github.com/makerspet/oomwoo/issues) or
[Discussion](https://github.com/makerspet/oomwoo/discussions) before writing
code, so we align on the approach and avoid wasted effort.
3. *Build it in your own repo.* For code and simulation modules, develop your
package in your **own public repository** — you own it, version it, and keep the
credit. Build against the ROS2 interface contract in
[SOFTWARE_INTERFACES.md](SOFTWARE_INTERFACES.md) so your work stays interoperable
with other modules. (Docs and small reference material are handled differently —
see below.)
4. *Submit a pointer PR.* Add a link to your repo in the module's entry with a
one-line description. It's small, easy to review, and lets several
implementations of the same module sit side by side. Keep the PR focused.
5. *Iterate in the open.* Modules are swappable — the best implementation surfaces
over time, with the maintainer having the last call. A design that isn't
selected is still a useful fallback.
## How contributions are structured
OOMWOO keeps the core small and lets the community grow around it:
- *Canonical / reference code stays first-party.* [oomwoo-one](https://github.com/makerspet/oomwoo-one)
(robot description + sim), [oomwoo-install](https://github.com/makerspet/oomwoo-install)
(dev environment), and the `kaiaai_*` packages are maintained by the project so the
out-of-the-box build always works.
- *Module implementations (code) live in your repo.* You build a competing
implementation of a module — a sim, a navigation stack, a behavior — in your own
repository and submit a *link*. The project features accepted work from the
module's page, credited to you. When a contribution is *featured*, we pin a
specific commit or tag (and may fork it into the makerspet org) so the reference
build stays reproducible even if the upstream repo moves.
- *Docs, specs and small reference material stay in-tree.* Part specifications,
datasheets, STEP-model sourcing notes, PCB notes, and benchmarks are lightweight
and best kept alongside the project — contribute those under
`contributions/<module>/<your-username>/` as files in a PR.
Why links for code? You keep ownership, credit, and freedom to iterate; the project
stays lean and avoids absorbing third-party code and its licensing; and multiple
implementations of a module can coexist and be compared. The shared
[SOFTWARE_INTERFACES.md](SOFTWARE_INTERFACES.md) contract is what keeps
independently-built modules compatible.
## Hardware contributions
For CAD and mechanical work, please include source files (not just exported STLs)
where possible, so others can modify your design. Note the tool and version you
used. If your change affects the bill of materials, mention it in the PR. Each
hardware module must stay within the mechanical/electrical interfaces in
[ARCHITECTURE.md](ARCHITECTURE.md).
*Safety:* battery, charging, motor-driver, and mains-adjacent modules require a
maintainer safety review before merge. Include a hazard note in your submission.
## Code style
Conventions are still being established. For now: keep it simple, readable, and
consistent with the surrounding code. ROS2 packages should follow standard ROS2
layout and naming. We'll formalize linting and style as the codebase grows.
## Licensing
By contributing, you agree that your contributions are licensed under the
project's [Apache License 2.0](../LICENSE). Hardware design files will be released
under an open hardware license (to be finalized); contributions of hardware
files are made on that same open basis.
## Community and conduct
Be respectful, helpful, and welcoming. We want OOMWOO to be an easy, friendly
place for makers of every skill level. Harassment or hostility isn't tolerated.
Questions? Open a [Discussion](https://github.com/makerspet/oomwoo/discussions?discussions_q=)
or join us on [Discord](https://discord.gg/3y2JKz5T25).
+64
View File
@@ -0,0 +1,64 @@
# RFC Backlog (planned / not-yet-active)
Planned modules that are *not yet active RFCs*. The RFCs ready to work on *now* live in the
[README status table](../README.md#requests-for-contributions), and each active RFC's full
spec is under [contributions/](../contributions). An item here graduates into an active RFC
(its own `contributions/<module>/` folder) once it is unblocked and ready.
*Phase legend:* `MVP` = targeted for the bare-bones build · `P2` = next phase ·
`P3+` = later. *Safety* = requires a maintainer safety review.
## On hold (have an RFC, paused pending sourced parts + a 3D reference design)
| Module | RFC | Notes |
|---|---|---|
| Dust bin 3D design | [dust-bin](../contributions/dust-bin) | Design / print / test the dust bin — waits on sourced parts + a 3D design |
| Blower fan assembly | [vacuum-fan](../contributions/vacuum-fan) | Fans already sourced (see BOM); the volute / gasket housing waits on the 3D design |
## Planned hardware (mechanical design)
Waits on sourced parts + a 3D reference-design sketch, then becomes an active
`contributions/` RFC. Part *specs* and *STEP models* are already active — see
[part-specs](../contributions/part-specs) and [source-3d-models](../contributions/source-3d-models).
The motor-driver / power PCB and battery charging are now the active
[io-pcb](../contributions/io-pcb) RFC.
| Module | ID | Phase | Notes |
|---|---|---|---|
| Chassis / base frame (reference) | `hw-chassis` | MVP | Integration backbone; defines the mechanical interface. Maintainer-owned. |
| Drive-wheel mounts (L/R) | `hw-wheel-mount` | MVP | Mount + suspension around the sourced wheel modules. |
| Compute mount (RPi 5) | `hw-compute-mount` | MVP | Mount + airflow for the Pi 5. |
| LiDAR mount | `hw-lidar-mount` | MVP | Centered turret; parametric for other models. |
| Main brush assembly | `hw-main-brush` | MVP | *Tapered rubber anti-tangle roller* + drive. |
| Bumper (mechanical + switches) | `hw-bumper` | MVP | Contact detection. |
| Cliff-sensor mounts | `hw-cliff` | MVP | IR drop detection at edges / stairs. |
| Top cover / shell | `hw-shell` | MVP | Cosmetic + protective; LiDAR clearance. |
| Wiring harness | `hw-harness` | MVP | Connector pinouts per [io-pcb](../contributions/io-pcb) + [part-specs](../contributions/part-specs). |
| Side brush | `hw-side-brush` | P2 | Edge cleaning. |
| Mop module (dual-spinning) | `hw-mop` | P2 | 3D-printed *dual-spinning* pads; skip the self-washing roller. |
| Charging dock (basic) | `hw-dock` | MVP | *Safety.* Contacts + alignment. |
| Charging dock (auto-empty) | `hw-dock` | P2 | |
| Charging dock (mop wash/dry) | `hw-dock` | P2 | |
## Planned software (later phase)
Foundational software (URDF, sim, SLAM, Nav2, coverage, teleop, sensors) is already covered by
the active RFCs. Remaining, later-phase work:
| Module | ID | Phase | Notes |
|---|---|---|---|
| Diagnostics / telemetry | `sw-diagnostics` | P2 | Health, logs. |
| Regression / CI tests | `sw-regression-tests` | P2 | Sim-based tests gating PRs. |
| Home Assistant integration | `sw-homeassistant` | P2 | MQTT / HA entity, map, control. |
| App runtime layer | `sw-app-runtime` | P3+ | ROS2-agnostic app sandbox. North star. |
| Web UI / dashboard | `sw-webui` | P3+ | Local control + map view. |
## Non-engineering contributions (also wanted)
| Track | Phase | Notes |
|---|---|---|
| 3D-print validation | MVP | Confirm parts print cleanly on common FDM printers. |
| Real-home testing | MVP/P2 | Build and report on real floors. |
| Docs / build guides | MVP | Turn working modules into step-by-step instructions. |
| Posts / videos / demos | ongoing | Content that grows the project (highly valued). |
| SOTA research | ongoing | Best-in-class prior art per module (part of each RFC). |
+159
View File
@@ -0,0 +1,159 @@
# OOMWOO ROS2 Software Interfaces
> Status: DRAFT. This is the shared software contract for simulation-first
> contributions. It reflects the current `urdf-gazebo-sim` package and should be
> updated whenever a module needs a new public topic, service, action, frame, or
> parameter.
## Purpose
OOMWOO software modules are meant to be swappable. A contributor should be able
to build `clean-and-map`, `nav-localize`, `recovery-safety`, `dock-cycle`, or
`cleaning-jobs` without depending on another module's private implementation.
This document defines the public ROS2 surface area those modules can share while
the hardware is still evolving. It is not a final hardware API.
## Naming rules
- Public names below are shown as root topics, for example `/scan`. Launch files
and node configs may use relative names such as `scan` when they resolve to
the same root topic in the default launch.
- Use REP-103 frames and SI units.
- Prefer standard ROS2/Nav2 message types before adding custom OOMWOO messages.
- If a module introduces a new public interface, document the producer, consumer,
message type, QoS expectation, and failure behavior in its README and update
this file.
- Simulation-only details, such as Gazebo collision entity names, must not leak
into cross-module contracts unless they are explicitly marked as simulation
diagnostics.
## Frames
| Frame | Owner | Meaning |
|---|---|---|
| `map` | SLAM/localization | Global map frame used by SLAM, AMCL, Nav2, and saved maps. |
| `odom` | Base odometry | Locally continuous odometry frame. |
| `base_footprint` | Robot description / odometry | Planar base frame for navigation. |
| `base_link` | Robot description | Main robot body frame. Hardware modules should reference this once geometry is frozen. |
| `base_scan` | Robot description | 2D LiDAR frame. |
Open decision: `base_link` origin, reference plane, robot diameter, and height
envelope are still defined in `ARCHITECTURE.md`.
## Baseline Topics
These topics are provided by the current Gazebo simulation or standard Nav2/SLAM
bringup and should be treated as the MVP baseline.
| Topic | Type | Direction | Producer | Consumers |
|---|---|---|---|---|
| `/cmd_vel` | `geometry_msgs/msg/Twist` | Command | Teleop, Nav2 velocity smoother, recovery nodes | Gazebo diff-drive / base controller |
| `/odom` | `nav_msgs/msg/Odometry` | State | Gazebo odometry / base controller | SLAM, AMCL, Nav2, recovery and job logic |
| `/tf` | `tf2_msgs/msg/TFMessage` | State | Robot state publisher, odometry, SLAM/localization | All pose-aware modules |
| `/joint_states` | `sensor_msgs/msg/JointState` | State | Gazebo joint state publisher / hardware base | Robot state publisher, diagnostics |
| `/scan` | `sensor_msgs/msg/LaserScan` | Sensor | 2D LiDAR / Gazebo LiDAR | SLAM, AMCL, Nav2 costmaps, wall following |
| `/map` | `nav_msgs/msg/OccupancyGrid` | State | SLAM or map server | Nav2, cleaning, zones, visualization |
| `/bumper_left` | `ros_gz_interfaces/msg/Contacts` in Gazebo | Sensor | Gazebo left contact sensor | Recovery, safety, clean-and-map obstacle handling |
| `/bumper_right` | `ros_gz_interfaces/msg/Contacts` in Gazebo | Sensor | Gazebo right contact sensor | Recovery, safety, clean-and-map obstacle handling |
### Bumper events
The current simulation publishes raw Gazebo contact messages:
- Message type: `ros_gz_interfaces/msg/Contacts`
- Contact list field: `contacts`
- Per-contact fields: `collision1`, `collision2`, `positions`, `normals`,
`depths`, `wrenches`
Consumers should treat `len(msg.contacts) > 0` as a bumper event after filtering
out ground-plane contacts. Do not read a `collisions` field; that belongs to the
single-contact message type and is not what the bridge publishes.
Hardware may eventually replace raw Gazebo contacts with a normalized bumper
message. Until that decision is made, module submissions should isolate the
Gazebo-specific parsing behind a small adapter.
## Nav2 Interfaces
Modules should reuse Nav2 actions and servers where possible.
| Interface | Type | Typical consumer |
|---|---|---|
| `/navigate_to_pose` | `nav2_msgs/action/NavigateToPose` | `nav-localize`, `cleaning-jobs`, `dock-cycle` |
| `/navigate_through_poses` | `nav2_msgs/action/NavigateThroughPoses` | Coverage, room jobs, dock approach |
| Nav2 behavior server | `spin`, `backup`, `drive_on_heading`, `wait` behaviors | Recovery and local fallback logic |
| Costmaps | Nav2 local/global costmap topics | Obstacle handling, zones, diagnostics |
| Map saver | Nav2 map saver service/CLI | `clean-and-map`, `nav-localize` |
If a module needs to command motion directly, it must define how it arbitrates
with Nav2 and recovery nodes so two nodes do not fight over `/cmd_vel`.
## Module Contracts
| Module | Inputs | Outputs / public behavior |
|---|---|---|
| `urdf-gazebo-sim` | `/cmd_vel` | Publishes `/scan`, `/odom`, `/tf`, `/joint_states`, `/bumper_left`, `/bumper_right`; provides worlds and robot description. |
| `clean-and-map` | `/scan`, `/odom`, `/tf`, bumper events, optional Nav2 actions | Drives first-pass coverage, produces a complete map, defines a done condition, saves map artifacts. |
| `nav-localize` | Saved map, `/scan`, `/odom`, `/tf`, Nav2 bringup | Provides known-map navigation, relocalization, and map-resume behavior. |
| `recovery-safety` | Bumper events, future cliff/wheel-drop/pickup/e-stop signals, Nav2 failures | Stops or gates motion, runs bounded recoveries, publishes clear pause/error status. |
| `floor-care` | `/scan`, map/coverage context, future surface sensor | Provides wall/edge following, surface classification, and mop actuator decisions. |
| `cleaning-jobs` | Saved map, zones, coverage progress, battery/bin/mop status, Nav2 actions | Provides start/pause/resume/cancel/status job behavior suitable for a future Home Assistant layer. |
| `dock-cycle` | Nav2/localization, dock marker, battery/service state | Provides undock, return-to-dock, precise docking, recharge/service completion, and find-dock fallback. |
| `live-robot-bringup` | Hardware drivers, same logical topics | Validates that hardware exposes the same public interfaces as the simulation. |
## Status and Errors
The final robot status API is still open. Until it is selected, modules that
need status reporting should document:
- `state`: short machine-readable state, for example `cleaning`, `recovering`,
`paused`, `docked`, or `error`
- `reason_code`: stable machine-readable reason, for example `BUMPER_STUCK`,
`LOCALIZATION_LOST`, or `LOW_BATTERY`
- `message`: human-readable explanation
- `recoverable`: whether a resume command is expected to work
- `source`: module name that produced the status
Open decision: choose the transport and type for cross-module status, likely a
standard diagnostic message or a small OOMWOO-specific message package.
## QoS and Parameters
- `use_sim_time` should be true in simulation launch files.
- Sensor streams such as `/scan` should use sensor-data QoS where configurable.
- `/map` and saved-map metadata should be available to late joiners where the
producer supports transient-local durability.
- Command topics should use small queues and should fail safe: stale commands
must not keep the robot moving.
## Validation Checklist
A module submission that depends on the MVP simulation should document how to
check the interfaces it uses. At minimum:
```bash
ros2 topic list
ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 topic echo /bumper_left
ros2 topic echo /bumper_right
ros2 run tf2_tools view_frames
```
For Nav2-based modules, also document how to send a `NavigateToPose` goal and
how to confirm `/cmd_vel` arbitration is safe.
## Miscellaneous
- PR your packages into the official distribution [makerspet/oomwoo-install](https://github.com/makerspet/oomwoo-install)
- put your ROS2 packages under `/ros_ws/src` (don't create another colcon workspace under `~/`)
- follow conventions of being able to select a robot package using `kaia config robot.model oomwoo_one`, see [tutorial](https://makerspet.com/blog/simulate-oomwoo-one-robot-vacuum-in-gazebo-with-ros-2/)
## Open Decisions
- Final hardware bumper/cliff/wheel-drop message shape.
- Robot-wide status/error message type and topic.
- Battery, dust-bin, mop, and dock service state interfaces.
- Localization-confidence interface for relocalization and kidnap detection.
- Job action/service API for start, pause, resume, cancel, and status.
+169
View File
@@ -0,0 +1,169 @@
# Write Your First OOMWOO ROS 2 Package: Cover the Floor While Mapping
> *Draft for makerspet.com (WordPress / Gutenberg).* Post 2 of 2. Builds on Post 1
> (*Simulate the oomwoo-one Robot Vacuum in Gazebo with ROS 2*). Here you'll write a small
> *pure-ROS 2* package that drives `oomwoo-one` on a coverage path *while* SLAM maps the
> room, and launch it all with one `ros2 launch`.
This is the "hello world" of developing for OOMWOO. It's deliberately simple — a reactive
"drive forward, turn when blocked" coverage that, combined with SLAM, maps and roughly covers
a room. Proper boustrophedon coverage is the [clean-and-map
RFC](https://github.com/makerspet/oomwoo/tree/main/contributions/clean-and-map); this teaches
you the mechanics of writing and launching an OOMWOO node.
## Prerequisites
The dev environment from *Post 1* (the `makerspet/oomwoo:jazzy-dev` container running, with
`kaia config robot.model oomwoo_one` set). Everything below runs *inside the container*.
## 1. Create a ROS 2 package
```
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python oomwoo_coverage --dependencies rclpy sensor_msgs geometry_msgs
```
## 2. Write the coverage node
Create `~/ros2_ws/src/oomwoo_coverage/oomwoo_coverage/coverage_node.py`:
```python
import math
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist
class Coverage(Node):
"""Drive forward; when the path ahead is blocked, turn until it clears.
Combined with SLAM, this bounces around a room and maps it."""
def __init__(self):
super().__init__('coverage')
self.pub = self.create_publisher(Twist, 'cmd_vel', 10)
self.create_subscription(LaserScan, 'scan', self.on_scan, 10)
self.clear = True
self.turn_dir = 1.0
self.create_timer(0.1, self.tick) # 10 Hz control loop
def on_scan(self, msg):
n = len(msg.ranges)
if n == 0:
return
# Check the forward +/- 25 deg sector (forward = scan index 0 for this LiDAR;
# adjust 'center' if your scan's zero points elsewhere).
sector = int(math.radians(25) / msg.angle_increment)
idxs = [(i) % n for i in range(-sector, sector + 1)]
fwd = [msg.ranges[i] for i in idxs
if msg.range_min < msg.ranges[i] < msg.range_max]
was_clear = self.clear
self.clear = (min(fwd) > 0.35) if fwd else True
if was_clear and not self.clear:
self.turn_dir *= -1.0 # alternate turn direction each time we hit something
def tick(self):
cmd = Twist()
if self.clear:
cmd.linear.x = 0.20 # m/s forward
else:
cmd.angular.z = 0.8 * self.turn_dir # rad/s turn until clear
self.pub.publish(cmd)
def main():
rclpy.init()
node = Coverage()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
```
## 3. Register the node
In `~/ros2_ws/src/oomwoo_coverage/setup.py`, add the console script:
```python
entry_points={
'console_scripts': [
'coverage = oomwoo_coverage.coverage_node:main',
],
},
```
## 4. Add a launch file (sim + SLAM + your node, in one command)
Create `~/ros2_ws/src/oomwoo_coverage/launch/coverage.launch.py`:
```python
import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, TimerAction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
gazebo = IncludeLaunchDescription(PythonLaunchDescriptionSource(
os.path.join(get_package_share_directory('kaiaai_gazebo'),
'launch', 'world.launch.py')))
nav = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(get_package_share_directory('kaiaai_bringup'),
'launch', 'navigation.launch.py')),
launch_arguments={'use_sim_time': 'true', 'slam': 'True'}.items())
coverage = Node(package='oomwoo_coverage', executable='coverage',
name='coverage', parameters=[{'use_sim_time': True}])
# give Gazebo + SLAM ~12 s to come up before the robot starts moving
return LaunchDescription([gazebo, nav, TimerAction(period=12.0, actions=[coverage])])
```
Reference the launch folder in `setup.py` `data_files` so it installs:
```python
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
```
(add `import os` and `from glob import glob` at the top of `setup.py`).
## 5. Build and run
```
cd ~/ros2_ws
colcon build --packages-select oomwoo_coverage
source install/setup.bash
ros2 launch oomwoo_coverage coverage.launch.py
```
Gazebo and RViz open, SLAM starts, and after ~12 s oomwoo-one begins driving itself —
forward until it's blocked, then turning — while the map fills in. Open RViz
(`ros2 launch kaiaai_bringup monitor_robot.launch.py use_sim_time:=true` in another shell) to
watch the map grow.
## 6. Save the map
```
ros2 run nav2_map_server map_saver_cli -f ~/maps/map
```
## What you just learned
- created a ROS 2 package, wrote a node that *subscribes to `/scan` and publishes `/cmd_vel`*,
- combined *your code + SLAM* in a single `ros2 launch`,
- produced a real map from an autonomous run.
That's the whole loop of developing for OOMWOO. From here, the natural next step is *real
coverage path planning* (boustrophedon, wall-following, frontier exploration) — which is
exactly the [clean-and-map RFC](https://github.com/makerspet/oomwoo/tree/main/contributions/clean-and-map).
Pick it up (or another module) from the [Requests for
Contributions](https://github.com/makerspet/oomwoo#requests-for-contributions), and come build
with us on [Discord](https://discord.gg/3y2JKz5T25).
+127
View File
@@ -0,0 +1,127 @@
# Simulate the oomwoo-one Robot Vacuum in Gazebo with ROS 2
> *Draft for makerspet.com (WordPress / Gutenberg).* Post 1 of 2: set up the OOMWOO
> software dev environment and drive `oomwoo-one` in simulation — no robot required.
> (Post 2 teaches you to write your own OOMWOO code.)
[OOMWOO](https://github.com/makerspet/oomwoo) is an open-source robot vacuum you build
yourself. *oomwoo-one* is the first model. This tutorial gets its ROS 2 simulation running
in *Gazebo* so you can develop mapping, navigation, and cleaning behaviours with *no
hardware* — everything runs in Docker on *Ubuntu or Windows*.
You'll get: SLAM mapping, autonomous Nav2 navigation, manual driving, and bumper sensors —
the same interfaces the real robot will expose.
## Prerequisites
- *Docker* — Docker Desktop on Windows/macOS, Docker Engine on Linux.
- *An X server* for the GUI windows (Gazebo, RViz):
- *Windows:* [VcXsrv](https://sourceforge.net/projects/vcxsrv/) (XLaunch).
- *Linux:* native X — nothing to install.
- No physical robot.
## 1. Start the X server (Windows only)
Launch *XLaunch* (from VcXsrv) and accept the defaults *except*:
> On the *"Display settings"* page, set *Display number = `0`* (not `-1`).
> The Docker container connects to `host.docker.internal:0.0`, so the display number *must
> be 0* or no GUI windows will appear.
Also tick *"Disable access control"* on the "Extra settings" page so the container can
connect. Finish the wizard — a tiny X icon appears in your tray.
On *Linux*, instead allow local Docker to reach your X server:
```bash
xhost +local:docker
```
## 2. Pull the OOMWOO Docker image
```
docker pull makerspet/oomwoo:jazzy-dev
```
## 3. Start the container
*Windows (PowerShell):*
```powershell
docker run --name makerspet -it --rm -v c:\maps:/root/maps -p 8888:8888/udp -p 5555:5555/udp -e DISPLAY=host.docker.internal:0.0 -e LIBGL_ALWAYS_INDIRECT=0 --add-host=host.docker.internal:host-gateway makerspet/oomwoo:jazzy-dev
```
(`DISPLAY=...:0.0` matches the XLaunch *display 0* from step 1.)
*Ubuntu / Linux:*
```bash
docker run --name makerspet -it --rm -v ~/maps:/root/maps -p 8888:8888/udp -p 5555:5555/udp -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --network host makerspet/oomwoo:jazzy-dev
```
Need more terminals into the same container? Open another PowerShell/terminal and run:
```
docker exec -it makerspet bash
```
## 4. Select the oomwoo-one model
Inside the container:
```
kaia config robot.model oomwoo_one
```
## 5. Launch the Gazebo world
```
ros2 launch kaiaai_gazebo world.launch.py
```
A Gazebo window opens with oomwoo-one in a living-room world.
## 6. Start SLAM mapping
In a new container shell (`docker exec -it makerspet bash`):
```
ros2 launch kaiaai_bringup navigation.launch.py use_sim_time:=true slam:=True
```
## 7. Open the RViz monitor
```
ros2 launch kaiaai_bringup monitor_robot.launch.py use_sim_time:=true
```
Watch the map build as the robot moves.
## 8. Drive it manually
```
ros2 run kaiaai_teleop teleop_keyboard
```
Use the keyboard to drive oomwoo-one around and fill in the map.
## 9. Autonomous navigation
In RViz, click *"Nav2 Goal"* and click-drag a destination — oomwoo-one plans a path and
drives there on its own.
## 10. Check the bumper sensors
```
ros2 topic echo /bumper_left
ros2 topic echo /bumper_right
```
Drive into a wall and watch the left/right contact events fire.
## 11. Save your map
```
ros2 run nav2_map_server map_saver_cli -f ~/maps/map
```
On Windows the map lands in `c:\maps`; on Linux in `~/maps`.
## What's next
You now have a full oomwoo-one simulation: SLAM, Nav2, teleop, and bumpers, exactly the
interfaces the real robot exposes. In *Post 2* you'll write your *first OOMWOO ROS 2
package* — a node that drives a coverage path *while* mapping — and launch it with
`ros2 launch`.
Want to help build OOMWOO? Grab a module from the [Requests for
Contributions](https://github.com/makerspet/oomwoo#requests-for-contributions) or say hi on
[Discord](https://discord.gg/3y2JKz5T25).
+332
View File
@@ -0,0 +1,332 @@
# OOMWOO Design Document
> *Working design doc.* This accumulates research-backed design decisions before
> they're split into per-module [RFCs](../contributions) and the [RFC backlog](RFC_BACKLOG.md), and/or migrated into
> the [README](../README.md). A condensed version of §1 already lives in the
> README's [Design research](../README.md#design-research) section. Expect this to
> grow as remaining components are researched.
---
## 1. Design research — what makes vacuum users happy
We reviewed the 20252026 consumer robot vacuum landscape (global + China-sourceable
brands, budget → flagship) across RTINGS, VacuumWars, Reddit r/robotvacuums, and
reviewer blogs, then adversarially fact-checked the key claims. The point: copy the
solutions that correlate with happy users, skip the ones that need commercial scale.
### Suction — a sourcing problem, not an engineering one
Real-world cleaning does *not* track advertised suction (Pa). ~$500 mid-tier
models beat flagships in pickup tests. A moderate *sealed* sourced motor + a good
brush + a *tight airflow seal* matches flagship cleaning — *no custom-molded
impeller needed.* Airflow sealing (bin/fan/brush seams) matters more than raw Pa.
[(source)](https://www.thesmarthomehookup.com/the-best-300-600-robot-vacuums-they-beat-the-flagships/)
### Navigation & "never gets stuck" — the #1 user pain, hardest to replicate
Best obstacle avoidance comes from *sensor fusion* (LiDAR + a floor-level 3D-ToF
or RGB camera + AI object recognition), not any single sensor. LiDAR is structurally
*blind below its ~10 cm turret*, which is exactly why robots eat cables and socks.
The Eufy Omni S2 ($1,599) was the only model in one test to pass all 24 obstacles —
and it has the full vision stack. *Never-stuck is commercial-scale.*
- *For OOMWOO:* v1 leans on the *bumper* for low/LiDAR-invisible obstacles (this
is already how the [clean-and-map RFC](../contributions/clean-and-map) handles it).
Camera + AI vision avoidance is a *later / experimental* goal, not an MVP promise.
Don't position OOMWOO as out-navigating commercial flagships; position it as an
open platform to *experiment* with navigation and vision.
[(source)](https://vacuumwars.com/best-robot-vacuums-with-obstacle-avoidance/)
### Brush — anti-tangle is what users notice
Rubber beats bristle, and a *tapered, one-side-mounted roller* resists hair-wrap
best (hair tangling is a top complaint). Easy to 3D-print or source compatible.
### Mop — dual-spinning is the replicable sweet spot
Performance ladder: flat drag pad (worst) → *dual spinning pads* (mid) →
self-washing roller (best). But the roller mop's "better stains / less residual
water" edge was *refuted* under fact-checking — it's overstated. A 3D-printed
*dual-spinning* mop is competitive and DIY-able; *skip the self-washing roller*
(and its multifunction wash/dry dock) for now.
[(source)](https://vacuumwars.com/robot-vacuum-mop-systems/)
### Dock — basic is DIY-able, full-service is not
A *basic charging dock* is well within reach (print the housing; source contacts +
adapter + IR beacon). *Auto-empty / mop-wash / hot-dry all-in-one docks* are
commercial-scale — defer, or use an off-the-shelf corded vac for emptying.
### Cloud-free / local control — the real differentiator
[Valetudo](https://github.com/Hypfer/Valetudo) gives cloud-free MQTT/REST local
control across ~10 brands. *Dreame* is the most rootable (≈16 models) and the
safest donor to study. Cloud-free local operation is OOMWOO's positioning advantage.
### Well-loved models worth studying
Eufy Omni S2 (obstacle avoidance), Narwal Flow (roller mop), Ecovacs Deebot T90 Pro
Omni (~$499 all-rounder), Dreame X40 Ultra (dual-spinning mop; Dreame = best donor).
> *Caveats:* the top-level dimensions (suction-decoupling, sensor-fusion, mop
> ladder) are primary-source and verified. Per-model rankings are *directional*,
> from single-run reviewer tests. Rootability is *per hardware revision* —
> re-verify any specific donor unit before buying to root.
---
## 2. Print vs source strategy
*Rule of thumb: print geometry, source mechanisms and wear items.* Anything with a
gearbox, encoder, rubber compound, spring, pump, or bearing is precision you can buy
for a few dollars; anything custom-shaped that mates with the OOMWOO chassis, print.
| Component | Source or 3D print | Why / how |
|---|---|---|
| *Driving wheel assemblies* | *Source (whole module)* | Complete drive modules (gearmotor + encoder + suspension + rubber tire). 3D print at most an adapter bracket. Why? Requires advanced skill - possibly SLA for gearbox, FDM TPU tire. The #1 "don't print it" part. |
| *Universal / caster wheel* | *Print* or source wheel/ball caster. | Likely a simple passive swivel, possibly TPU. |
| *Side brush assembly* | *Hybrid* | Source brush + small gearmotor; print the mount. Fit a *common replaceable brush*. Fixed (not extendable) for v1. |
| *Main brush* | *Source / hybrid* | Tapered rubber anti-tangle roller in a *common wear-part size*; source compatible, or print core + rubber. |
| *Bumper* | *Hybrid* | Print floating shroud; source lever microswitches + return springs. |
| *Dust bin / water tank* | *Print body + source guts* | Print custom body (mates airflow); source filter (common HEPA size), gasket (or TPU print), latch spring. Water tank adds a sourced *pump + solenoid valve + tubing*. |
| *Mop lift* | *Hybrid (P2)* | Print cam/linkage; source a small *servo or geared motor*. |
| Mop disposable cloths | *Source* | Source (easier) or DIY sew. |
| Mop dryer | *Source* | Source the mop dryer. |
| *Enclosure / top shell* | *Print* | Custom cosmetic/structural; no off-the-shelf equivalent. Design for splitting to fit common print beds. |
| *Dock (basic charge)* | *Print housing + source contacts* | Source *pogo pins / spring contacts / magnets* (magnets can carry [10 A](https://xdaforums.com/t/home-made-pogo-pin-charging-dock.2019847/)) + wall adapter + IR-beacon LEDs. Plenty of [DIY precedent](https://www.instructables.com/Roamer-the-Self-Charging-Companion-Robot/). |
| *Auto-empty dock* | *3D print enclosure + source bin/fan* | Needs its own fan + bin (commercial-scale). Off-the-shelf corded vac bolted to a printed dock is the DIY path. |
| *Mop dock* | *3D print enclosure + source water tanks/hookups* | Needs its own fan + bin (commercial-scale). Off-the-shelf corded vac bolted to a printed dock is the DIY path. |
| Battery, LiDAR, motors, PCB, fasteners, bearings, gaskets | *Source* | Standard sourced parts (custom PCB + LiDAR aside). |
| Single Board Computer | *Source* | Raspberry Pi 5 4GB or better for first model. |
| Input/Output board | *Custom* | No DIY-vacuum I/O PCBs I'm aware of. I'll design a custom PCB for sensors and motor drivers. |
| Cameras, sensors, LiDAR | *Source* | Color + distance cameras for top-tier obstacle avoidance. IR cliff, side proximity sensors. Ultrasonic carpet sensor. |
*Sourcing strategy:* deliberately spec sourced wear parts (brushes, filters, wheel
modules) in *common, abundant sizes* so users buy cheap "universal" / Roomba-style
replacements anywhere. A selling feature *and* less inventory to stock.
---
## 3. Feature decisions
### 3.1 Extendable side brush — *fixed for v1*
The extendable arm (e.g. Roborock FlexiArm) is a *genuinely loved* feature — best
corner-cleaning scores and strong reviews
[(source)](https://vacuumwars.com/vacuum-wars-best-robot-vacuums/). But it's a
mechanically complex actuated mechanism. A well-placed *fixed* side brush captures
most of the corner benefit at a fraction of the complexity. Extendable is a great
*P2+ community mod*, not an MVP requirement.
### 3.2 Body shape — *round*
D-shape cleans corners/edges better, but the real-world advantage is "smaller than
most people expect," and a well-engineered round robot with good side brushes
performs as well or better in most homes
[(source)](https://www.ecovacs.com/us/blog/round-vs-square-robot-vacuum). Round wins
for a DIY/printable platform:
1. Simpler to design, print, and seal.
2. Navigates better — rotates in place, backs out the way it came (D-shape
complicates every nav/recovery RFC).
3. Natural fit for the spinning 2D LiDAR turret.
4. Matches the round teardown reference we're porting.
5. Corners are better solved by the side brush (later extendable) than by body shape.
*Decision: round body + fixed side brush now, extendable side brush later.*
---
## 4. Compute (SBC)
*v1: Raspberry Pi 5 (4 GB).* Chosen to onboard the large Raspberry Pi community —
the biggest contributor wedge. Runs ROS2 + LiDAR SLAM + Nav2 comfortably. No on-board NPU.
- *ML vision option — Hailo AI HAT.* Add the RPi M.2 AI Kit (Hailo-8L, ~13 TOPS) for
real-time camera obstacle detection: keeps the entire RPi ecosystem advantage *and*
gains an NPU. *Check the board-stack height* — a vacuum is only ~10 cm tall and
the M.2 HAT adds Z. Evaluate alternatives (Coral USB/M.2 accelerator, or lighter
CPU-only models) against the height budget.
- *Later: Rockchip RK3588 / RK3576.* Orange Pi 5, Radxa Rock 5B, Banana Pi CM5 —
built-in *6 TOPS NPU*, RKNN toolkit, YOLO ~65 ms/image. Cheaper and integrated, but
driver / ROS2 / kernel maturity *lags RPi*, so it's a *later* move, not the
community-onboarding one.
---
## 5. I/O board (custom, JLCPCB)
No off-the-shelf DIY-vacuum I/O board exists, so we design one. It carries an *MCU
running micro-ROS* that talks to the SBC over a fast serial / USB link: the SBC does
ROS2 / SLAM / nav / vision; the board does real-time motor + sensor I/O.
*MCU:* STM32G070RBT6 (LQFP64, ~$0.93 @ 100 pcs) — cheap, lots of peripherals.
*Pin-budget risk:* the full peripheral set below is a lot for 64 pins. Do a
pin-allocation spreadsheet before committing. Offload the *BLDC fan to an external
ESC* (1 PWM pin instead of in-MCU FOC) to save pins and complexity. If still tight,
consider the STM32G0B1RET6 (same family, more peripherals/RAM) or a 100-pin part.
*LiDAR (3irobotix CRL-200S):* wires to the I/O board. The board *drives the LiDAR
motor* (MOSFET + closed-loop speed control using the LiDAR's RPM feedback) and *passes
raw LiDAR data through the MCU to the SBC* over the fast serial link. *Confirm the MCU
UART bandwidth handles the CRL-200S data rate.*
Cross-checked peripheral list (your running list + `[+]` = additions to consider):
*Sensors*
- 2D LiDAR (CRL-200S) — motor-driven + data passthrough to SBC
- IR cliff / proximity / docking
- Bumper micro-switches (+ optional optical/IR bumper)
- Wheel encoders
- Carpet ultrasonic sensor
- Color camera → *connects to the SBC's CSI/USB, not this board*
- VL53L7CX distance sensor (I²C)
- Water tank level (full/empty)
- Dust bin present / lid open-closed
- Battery level / fuel gauge
- Dock-connected / charge sense
- IMU
- `[+]` Wheel-drop / lift sensor (robot picked up) — distinct from cliff
*Motor drivers*
- Main wheels ×2
- Main brush
- Side brush(es)
- `[+]` *Mop pad spin motor(s)* — for dual-spinning pads (you listed mop *lift* but not *spin*)
- Mop lift servo
- Water pump (low-side MOSFET)
- Vacuum fan (BLDC) — via *external ESC* (recommended)
- LiDAR motor (MOSFET, closed-loop)
*Power*
- Battery connector
- Charging circuit (charge-controller IC)
- Dock contacts
- BMS interface
- Current sense (per-rail and/or per-motor for stall / tangle detection)
- DC-DC converters (5 V for SBC, 3.3 V logic, motor rails)
- Power on/off button + soft-latch power circuit
- `[+]` Protection: reverse-polarity, over-current fuse / eFuse, inrush limiting
*Audio / UI*
- Speaker amp + connector (audio in from the SBC)
- Mic placeholder
- Power / status LEDs
- Buttons (power, dock, clean)
- `[+]` Buzzer (cheap fallback if the speaker amp is deferred)
*Host link:* USB or high-speed UART between MCU and SBC, carrying micro-ROS *and*
the LiDAR passthrough — confirm bandwidth covers both.
> *Scope note:* the *wash/dry dock has its own controller* (ESP32 + WiFi) for its
> pumps / heater / fan / water-level. Those are *not* on the robot I/O board.
---
## 6. Electrical / sensor BoM sketch
Rough *robot* BoM at prototype / low-qty China-sourcing prices (compresses at volume).
Excludes the dock.
| Item | Qty | ~USD | Notes |
|---|---|---|---|
| Drive wheel modules | 2 | 1227 | sourced complete module |
| Caster wheel | 1 | 03 | print or ball caster |
| Suction blower (BLDC) | 1 | 820 | sealed sourced motor |
| Main brush + motor | 1 | 512 | tapered rubber roller |
| Side brush + motor | 12 | 38 | |
| Mop spin motor(s) + pads | 12 | 615 | mopping models |
| Water pump + valve + tubing | 1 | 410 | mopping models |
| Mop lift servo | 1 | 26 | mopping models |
| Battery pack (~14.8 V Li-ion) + BMS | 1 | 1530 | safety review |
| LiDAR (CRL-200S / LDS) | 1 | 3040 | your cost |
| VL53L7CX ToF | 1 | 815 | obstacle detection |
| Color camera | 1 | 515 | to SBC |
| IMU | 1 | 25 | |
| IR cliff / proximity | 34 | 38 | |
| Bumper micro-switches | 23 | 13 | |
| Ultrasonic carpet sensor | 1 | 25 | |
| Speaker + amp, mic, LEDs, buttons | — | 38 | |
| Custom I/O PCB (JLCPCB assembled, low qty) | 1 | 1540 | |
| Wiring, connectors, fasteners, magnets, gaskets, filter | — | 1225 | |
| Printed parts (filament) | — | 515 | |
| *Robot subtotal (sourced parts)* | | *~$130270* | excludes SBC |
| Raspberry Pi 5 4 GB | 1 | ~60 | |
| Hailo AI HAT (optional, premium vision) | 1 | ~70 | |
---
## 7. Water system
*Robot:* an onboard *clean-water tank* (printed) → *solenoid diaphragm pump* (what
commercial units use) → mop head. For spinning pads, dirty water stays in the pads until
the dock washes them.
*Dock tiers (where the water complexity lives):*
- *Auto-empty:* dock fan + bin/bag suck the robot's dustbin out through a sealed port.
The off-the-shelf-corded-vac approach works here.
- *Wash + dry:* *clean tank + dirty tank* (printed) + *two pumps* + a wash
tray/roller + a *hot-air blower (heater + fan)*. Wash *must* include hot-air dry —
wash-without-dry breeds mildew/odor. Needs its *own ESP32 + WiFi controller*.
- *Plumbing hookup:* offer as an *option* on the premium dock (pump + supply/drain
lines + valve); default to tank-based (direct plumbing needs pro install). Skip exotic
water-recycling (e.g. silver-ion distillation).
- DIY parts are cheap/common: 12 V diaphragm or peristaltic pumps, solenoid valves,
silicone tubing, float / capacitive level sensors.
---
## 8. Budget target
*Target: ~$100200 sourced parts + Raspberry Pi 5 4 GB*, aiming at the capability of a
mid-range ($500600) commercial vacuum.
- *Verdict: realistic for the mechanicals + core sensors* at the lowmid end, and it
compresses at volume. But *mopping + premium obstacle detection* (ToF + color camera
+ an NPU accelerator) are exactly the line items that push toward / past $200 — the
Hailo HAT alone is ~$70. So $100200 holds for a capable vacuum; "premium vision now"
wants a +$70-ish allowance (or defer the NPU to the Rockchip generation).
- *Sanity check vs commercial:* a $500600 LiDAR vacuum has roughly a *$120180 FOB
BoM at 5,000 MOQ*. Our low-qty numbers are coherent — we trade *no tooling/molds*
(3D print) for *higher per-part cost* (no volume).
- *Set builder expectations honestly:* total DIY spend (kit + RPi 5 + LiDAR + margin)
lands *above* a commercial unit's BoM. The value proposition is *openness, local
control, and hackability — not beating Roborock on price.*
---
## 9. Product lineup — shared base, three dock tiers
One robot base, three dock tiers, released in order:
1. *Basic charging dock* (first release — simplest, MVP-aligned).
2. *Auto-empty dock* (dust).
3. *Auto-empty + mop-wash + hot-dry dock* (the full-service tier).
Notes:
- Mirrors how commercial brands segment (same robot, dock tiers) and fits the
swappable-module philosophy.
- The *mop hardware (spinning pads + onboard water tank) is an add-on module* on the
shared base — only the mopping models carry it; the base is otherwise constant.
- The *wash+dry dock is almost its own mini-product* (pumps, heater, tanks, ESP32).
Correctly the last and hardest deliverable after the robot itself.
- Skip a wash-*only* dock — drying is mandatory (odor).
---
## 10. Obstacle detection strategy (premium hardware + community ML)
Targets the #1 user pain (getting stuck / eating cables). Strategy: *premium hardware
now, ML maturity via community contribution over time.*
- *Sensors:* *VL53L7CX* multizone ToF (8×8, *90° FoV*, ~350 cm — wider FoV than the
L5CX's 63°, better coverage) *+ color camera.* The ToF alone detects the low / small /
cliff objects the *LiDAR is blind to*, so v1 gets real "doesn't eat cables" value
*before* any ML matures — de-risks the feature.
- *ML:* the *camera + model* is where the NPU matters (Hailo HAT now, Rockchip NPU
later); real-time RGB detection won't run well on the RPi 5 CPU.
- *Community-labeled household-obstacle dataset* is a legitimately novel contribution
track — every contributor's home is training data. Treat it as a first-class
contribution track alongside the RFCs; it doubles as community-building.
---
## 11. Still to research / open decisions
- *Battery:* chemistry (Li-ion 3S/4S?), specific cells + BMS, charge profile — *safety review*.
- *BLDC fan ESC:* select an off-the-shelf ESC vs in-MCU drive.
- *Filter:* pick a common, abundant filter size as the interface standard.
- *Gasket sourcing* vs TPU printing for airflow seals.
- *Dock docking signal:* IR beacon protocol / fiducial / reflective marker.
- *Fastener / heat-set insert standard* (ties to ARCHITECTURE §5.2).
- *Confirm:* MCU pin budget, host-link bandwidth (micro-ROS + LiDAR passthrough), Hailo stack height.
- Remaining mechanical parts not yet covered above.