01 — Premise
Why root a vacuum
A modern robot vacuum has a LiDAR turret, a camera on some models, and a complete, continuously-updated floor plan of the inside of your house. All of that goes to a vendor cloud, and the app you control it with is a thin client over that cloud. If the vendor's servers go away, so does your vacuum's brain.
Rooting replaces that arrangement. Valetudo runs on the robot and exposes a local web interface plus MQTT, so Home Assistant talks to the machine directly on your LAN. No account, no outbound connection, no dependency on anyone's uptime.
The second reason is more honest: once it is rooted, it is an ARM Linux machine with a read-only rootfs, a writable data partition and a boot hook. Everything I learned making changes stick on that filesystem is the genuinely transferable part of this project, and it is most of what is below.
02 — Warning
Read this before you open it
There is no safe mode to fall back to. A bad boot script can brick the device outright. The official documentation says so directly, and the stock boot hook file ships with that warning written into it.
A factory reset clears the data partition — which is precisely why every customisation belongs inside the guard condition described in making changes survive a reboot. Put it there and a factory reset disables your changes and recovers the robot. Put it outside and a factory reset recovers nothing.
Full disassembly, conformal coating to scrape, a test point to short while the robot boots, and a firmware flash. The Valetudo maintainers say plainly that this method is not suited to beginners and that you should get help from someone experienced if you are not comfortable with Linux and hardware. That is good advice and I am repeating it rather than softening it.
What this page is and is not
This is a build log explaining how the process works and what I learned living on the rooted device. It is not a replacement for the official documentation, which is maintained, versioned per model, and correct in ways a blog post drifts out of. Follow these, in this order:
- Valetudo — Roborock installation guide — the authoritative, per-model instructions
- Rooting NAND-based Roborock robots via FEL — the detailed hardware walkthrough, with photographs of the test points
- Dustbuilder — builds the FEL image and the firmware package for your specific model
Check your exact model against the supported-robots matrix first. The S7 is NAND-based and takes the FEL path; other Roborock models root differently, and following the wrong procedure is how people damage hardware.
03 — Mechanism
How FEL rooting works
FEL is a recovery mode built into Allwinner system-on-chips. When the SoC starts and is told not to boot normally, it waits on USB for a host to hand it code to execute. It is a factory provisioning feature — and it is below the level of anything the vendor's firmware could refuse.
Why it takes two passes
The live image does not itself make the robot rooted. It runs in RAM, reaches into the robot's real filesystem, and opens a door — SSH. That door is enough to get in and install a proper rooted firmware built for your specific model, which is what actually persists.
Understanding that split is what makes the process legible instead of magic: pass one gets you a shell, pass two gets you a system.
What you need
- A Linux machine with
sunxi-tools≥ 1.3 - A micro USB cable (data, not charge-only — this catches people out)
- A jumper wire or a paperclip; anything grounded works for the GND side
- A FEL image and a firmware package built from Dustbuilder for your exact model
- Screwdrivers, and somewhere to lay out screws in disassembly order
# Debian / Ubuntu
sudo apt-get install sunxi-tools
# confirm the robot appears once it is in FEL mode
lsusb
04 — Procedure
The rooting procedure
Summarised so you know what you are in for. Do the actual run against the official FEL howto, which has the photographs.
-
Charge the robot fully
All of this happens on battery power — the robot is off the dock and open on your desk for the duration. Running out of charge partway through a flash is exactly the situation you do not want on a device with no recovery partition.
-
Build your images first
On Dustbuilder, select your model and tick Create FEL image. You want both the FEL image and the firmware package downloaded and verified before the robot is in pieces.
-
Photograph every step of the disassembly
Not optional advice. Which screw came from where, how the ribbon cables were routed, which connector faced which way. Reassembly is an hour of relief or an hour of guessing, and that is decided entirely by whether you took the photos.
-
Find TPA17 — and do not confuse it with TP17
The test point is on the underside of the mainboard. There is also a
TP17nearby. They are different pads and the official guide colour-codes them for exactly this reason —TPA17is the one marked red.The board is conformal-coated against moisture. You have to gently scrape the coating off the pad to make electrical contact. Gently is doing real work in that sentence; you are removing a lacquer, not the pad.
-
Short TPA17 to ground and power on
Connect
TPA17to any ground point. Hold it. Press the power button for 3 seconds, then keepTPA17grounded for about 5 more seconds. Then checklsusb— the SoC should now be enumerating in FEL mode. -
Run the live image
With
sunxi-tools, push the FEL image the builder produced. It boots in RAM, patches the robot's rootfs to enable SSH, and finishes. -
SSH in and install the firmware
The robot should now be reachable over the network. Transfer the firmware package from Dustbuilder and run its
install.sh. This is the step that makes root permanent. -
Reassemble, and set up Valetudo
Reassemble using your photographs. On first boot, Valetudo provides its own Wi-Fi provisioning flow; from then on it is a web interface on your LAN, and the vendor app is out of the picture entirely.
05 — The system
Anatomy of the rooted robot
Once you are in, the layout matters more than the fact that you got in. Everything about making changes stick follows from one fact: the root filesystem is read-only SquashFS.
| Path | What it is |
|---|---|
/mnt/data | The writable partition. Everything you add lives here. Cleared by a factory reset |
/mnt/reserve/_root.sh | The boot hook. Runs on every start. This is your entry point into the running system |
/mnt/data/valetudo | The Valetudo binary, launched by the hook |
/mnt/data/valetudo_config.json | Valetudo's configuration, passed in by environment variable |
/mnt/resources/ | Vendor resources — sounds and similar. Read-only, but mountable over |
/mnt/data/miio/wifi.conf | Provisioning config the hook rewrites to drop the vendor cloud path |
/mnt/data/authorized_keys | Your SSH public key, bind-mounted into place at boot |
The boot hook is the whole API
You cannot install a service the normal way, because there is nowhere to install it. What you get instead is one shell script that runs at startup, and everything the rooted system does for you happens inside it:
#!/bin/bash
if [[ -f /mnt/data/valetudo ]]; then
mkdir -p /mnt/data/miio/
# SSH key: the rootfs is read-only, so the key lives on the data
# partition and is bind-mounted over both locations Dropbear
# may consult. Each mount is guarded so rerunning is harmless.
if [[ -f /mnt/data/authorized_keys ]]; then
chmod 600 /mnt/data/authorized_keys
grep -q " /root/.ssh/authorized_keys " /proc/mounts || \
mount --bind /mnt/data/authorized_keys /root/.ssh/authorized_keys
grep -q " /etc/dropbear/authorized_keys " /proc/mounts || \
mount --bind /mnt/data/authorized_keys /etc/dropbear/authorized_keys
fi
# Steer provisioning away from the vendor cloud path.
if grep -q -e "cfg_by=tuya" -e "cfg_by=rriot" /mnt/data/miio/wifi.conf; then
sed -i "s/cfg_by=tuya/cfg_by=miot/g" /mnt/data/miio/wifi.conf
sed -i "s/cfg_by=rriot/cfg_by=miot/g" /mnt/data/miio/wifi.conf
fi
# Clear cleanup logs so Valetudo can update itself.
rm -r /mnt/data/rockrobo/rrlog/*REL
VALETUDO_CONFIG_PATH=/mnt/data/valetudo_config.json /mnt/data/valetudo >> /dev/null 2>&1 &
fi
Three techniques in one short script, and all three generalise well beyond this device.
-
Bind mounts over read-only
You cannot write to the rootfs, but you can mount something writable on top of a path. The file underneath is untouched; the kernel just resolves the path to yours instead.
-
Guarded, idempotent mounts
Every mount checks
/proc/mountsfirst. A hook that stacks mounts every run eventually fails in a way that is genuinely annoying to diagnose. -
Everything inside the guard
The whole body sits inside
if [[ -f /mnt/data/valetudo ]]. Wipe the data partition and the hook becomes a no-op — which is what makes a factory reset a working escape hatch.
06 — Technique
Making changes survive a reboot
This is the part that took me the longest to get right, and it is the part that transfers to any locked-down embedded Linux device.
The two failures everyone hits first
-
Writing to the rootfs
You SSH in, edit a file under
/mnt/resources, it appears to work, and it is gone after a reboot — or the write is refused outright. SquashFS is read-only by construction. Nothing you put there persists. Your files go under/mnt/data, always. -
Mounting over a directory with a partial copy
A bind mount replaces the directory. If you copy in only the four files you changed and mount that over a folder of eighty, the robot now has four files and seventy-six missing ones. Symptoms are weird and specific — some things work, others silently do nothing.
Seed from the stock layout first. Copy the entire original directory into your folder, then overlay your replacements on top of that complete set.
# seed the full stock layout, THEN overlay your own files
ssh root@robot "mkdir -p /mnt/data/mypack \
&& cp -a /mnt/resources/audio_default/. /mnt/data/mypack/"
# now copy your replacements over the complete copy
scp -O my_file.ogg root@robot:/mnt/data/mypack/sounds/
The race nobody warns you about
The boot hook runs early. The directory you want to shadow may not be populated yet — mount over it too soon and you have shadowed an empty mount point, which then gets populated underneath your mount where nothing can see it.
The fix is to wait for evidence that the real filesystem is up. Poll for a file you know belongs there:
if [[ -d /mnt/data/mypack ]]; then
# wait for the stock resources to actually exist
while [[ ! -f /mnt/resources/audio_default/power_on.ogg ]]; do sleep 1; done
sleep 1
# guard, then unmount whatever is there, then mount ours
if ! grep -q "/mnt/data/mypack on /mnt/resources/audio_default " /proc/mounts; then
umount /mnt/resources/audio_default 2>/dev/null || true
mount -o bind /mnt/data/mypack /mnt/resources/audio_default
fi
fi
A fixed sleep 10 is a guess that is either too short on a
cold boot or wasted time on a warm one. Waiting on a specific file is a
condition rather than a hope, and it is why the extra
sleep 1 after the loop is the only arbitrary delay in the
script — a small settle margin after the condition is genuinely met.
Iterating without rebooting
Testing a boot hook by rebooting the robot every time is miserable. The same mount commands work live over SSH, so the development loop is: apply the mount by hand, test, adjust, and only reboot at the end to prove the hook itself is correct.
ssh root@robot '
umount /mnt/resources/audio_default 2>/dev/null || true
mount -o bind /mnt/data/mypack /mnt/resources/audio_default
mount | grep audio_default
'
07 — Case study
Replacing every sound the robot makes
The concrete thing I built on top of all this: a complete custom voice pack. Every event sound the robot plays, replaced with clips cut from a track, built by a script and deployed with one command.
The build pipeline
- A JSON map of timestamps to output filenames — the whole design lives in one editable file
- A Python builder cutting each clip, normalising level, and encoding to OGG
- Local MP3 previews written first, so I can listen before anything touches the robot
- A deploy script that seeds the stock layout, overlays the pack, applies the bind mounts live, and reports the file count
The preview step is the one worth stealing. Cutting audio by timestamp is iterative — a clip is a quarter second late, or clips a word. Being able to hear the result locally turns a fifteen-minute round trip into a two-second one.
What I learned mapping events
Not every event you can see in the interface has a sound behind it. Changing fan speed in Valetudo produces no audio at all — the robot simply does not play anything for that transition. So the four fan-speed clips I had cut got remapped onto the clean-mode starts, which do fire.
That is the general shape of working on someone else's embedded system: the interface implies a set of events, and the firmware implements a different, overlapping set. You find the difference by testing each one and writing down which ones actually fired.
| Event | File | Fires? |
|---|---|---|
| Docked / charging | charging.ogg | Yes |
| Water tank inserted | water_box_carriage_in.ogg | Yes |
| Water tank removed | water_box_carriage_out.ogg | Yes |
| Clean started | start.ogg | Yes |
| Room / zone / spot clean | room.ogg, zone.ogg, spot.ogg | Yes |
| Returning to dock | home.ogg | Yes |
| Fan speed change | — | No sound exists |
Valetudo has a Speaker Test under Robot → Misc. Docking the robot and attaching or removing the mop tank covers three more event paths in about ten seconds. Between those you can verify most of a pack without running a single cleaning job.
08 — Status
Where it stands
Rooted, running Valetudo, no outbound cloud connections, integrated with Home Assistant over MQTT, and playing a full custom sound pack applied by a boot hook that survives reboots and updates.
What I would tell someone starting
- Read the official docs completely before touching a screwdriver. The whole thing, for your exact model. Not the summary — the actual guide.
- Photograph the disassembly. The single highest return-on-effort action in the entire project.
- Keep everything inside the guard condition. It is what makes a factory reset an escape hatch instead of a shrug.
- Test mounts live over SSH before trusting the hook. Rebooting to test a one-line change is how a Saturday disappears.
- A device with no recovery partition deserves respect. I read every script twice before it went in the boot path, and I would do that again.