OpenTapoVac
Disclaimer: parts of this file are AI-generated (Claude Opus 5.5), but all of it has been reviewed by a human.
Local control of a TP-Link Tapo robot vacuum (developed against the RV50 Pro Omni): a daemon, a command-line tool and a small web page for day-to-day cleaning, with house-specific rules kept in a config file.
Background
I was not doing my research properly, and ended up with the RV50 Pro Omni robot vacuum. I learned a few things about this device:
- The robot itself is quite decent.
- The software does not work out very well in my home. The firmware seems to be quite buggy; it's frequently doing weird things, particularly it has the tendency to not find the way back to the dock - sometimes searching the whole house without success even if the dock is very visible on the map. It's also aborting the whole run (e.g. "wash and then mop all the rooms") midway without any way to resume if anything goes wrong. It frequently needs to look around to "find my position", and it's not reliably finding the position.
- The firmware is locked down - not possible to fix it, hack it, nor replace it with Valetudo - we're stuck with the vendor-provided firmware. Hence, the "Open" part of this project is limited to the client-side software; this is not open firmware.
- The robot has problems with doorsteps.
- It is possible to reverse-engineer the cellphone app, and it is possible to control the device from the local network (see also python-kasa PR #1592).
- Apparently, if only using the robot in the late nights and early mornings, it will never empty the dust container - and it seems that the water container in the robot won't be refilled. Its clock was an hour off (UTC+1 while Oslo was on summer time), which pushed the quiet hours into the morning - whether it doesn't handle DST or just lost the update when its internet access was closed is not known.
This project has a few goals:
- Make workarounds for many of the issues found in the software
- Make a CLI for me
- Make a web interface for my family
- Leave enough documentation in the project that the AI can do everything that can be done from the app, if not more.
Status
"It seems to work" with my 'bot and my home. It has a daemon, a command-line tool and a web page. There are still some planned features that are missing. At the start I had the idea that there was no point trying to replace the app - just make an interface suitable for daily routines and for working around all the problems encountered. However, the app stopped working the moment I closed the egress for the vacuum, and I do find it a lot easier to do things from the laptop than from a cellphone app - so the long-term design goal now is to make the cellphone app completely obsolete.
Installation
From PyPI:
pipx install opentapovac # or: uv tool install opentapovac
From a checkout:
make install
(This auto-detects uv, pipx, or pip and does the right thing.)
Tab completion for bash and zsh comes with it; bash needs the
bash-completion package.
TPAP support is not in a python-kasa release yet, so the package carries
its own copy of the TPAP transport from the python-kasa PR branch
(src/opentapovac/_tpap.py, see docs/design.md, "Decisions") and runs it
on a released python-kasa.
NixOS
nix/package.nix builds the package and nix/module.nix runs the daemon as services.opentapovac:
imports = [ "${opentapovac-src}/nix/module.nix" ];
services.opentapovac = {
enable = true;
credentialsFile = "/etc/opentapovac/credentials.yaml"; # user: / pass:
settings = { robot.host = "192.0.2.10"; timezone = "Europe/Oslo"; };
};
settings is the config file below, as Nix. The daemon binds to
localhost; put nginx with authentication in front of it.
Configuration
~/.config/opentapovac/config.yaml; the account password stays in
~/.config/tapo/credentials.yaml (user: and pass:, mode 0600), or in
$KASA_USERNAME/$KASA_PASSWORD. A minimal config:
robot: {host: 192.0.2.10}
timezone: Europe/Oslo
defaults: {mode: vac_then_mop, suction: 2, water: 2, passes: 1}
rooms: # keyed by the robot's room name or id
"kjøkken": {aliases: [kitchen]}
6: {aliases: [outer hall]}
stairs: {forbidden: true} # refused without --force
"bedroom 2": {carry_in: true} # can't get in by itself
"living room": {carry_out: true} # can't get out by itself
"boys room": {home_route: [kjøkken]} # lost here: this route instead
order: {first: ["bedroom 1", 6], last: ["kjøkken"]} # within a run
presets:
- {label: "Halls + kitchen", rooms: [5, 6, "kjøkken"]}
waypoints: {home_route: [hall, kjøkken]} # lost on the way home: via these, then home
waypoint_timeout: 3m # a waypoint not reached in this long: an alert
listen: 127.0.0.1:8765 # the daemon
human_wait_timeout: 15m # then an alert; it keeps waiting
Room names come from the robot; opentapovac rooms --refresh reads them.
The poll interval and the monitor's timeouts can be set under monitor:
(see src/opentapovac/config.py).
Usage
Run opentapovac --help, or opentapovac COMMAND --help, for the full
list of options.
opentapovac clean kitchen "outer hall" # one run, rooms in this order
opentapovac clean --mop --sequential hall 6 # one run per room
opentapovac status | stop | home | rooms | log
opentapovac answer done # "carry the robot into ..."
opentapovac map map.png # map with the last 12 h of tracks
opentapovac goto hall | goto 4100 3000 # send it to a room or a point (mm)
opentapovac pause | resume
opentapovac serve # the daemon and the web page
Without a daemon, clean blocks until the robot is back on the dock and
reports what happens on the way (stuck, dock not found, relocation, empty
water tank). With a daemon running, every command goes through it and
clean returns at once; --wait follows the job. A job started while
another runs is queued and runs in turn; Stop, or a job that fails or is
stopped, drops the queue.
While the robot works, a progress line comes every minute (percent done, vacuuming or mopping, room, battery), with a warning when a vacuum pass is skipped or the robot makes no progress for five minutes. After a relocation, a "position found" line says which room the robot now thinks it is in.
When the robot ends a run without its mop pass, the mopping is sent
once more; when it leaves a run unfinished, the job asks whether to send
it again. Not with the battery low or the water tank empty.
redo_missed: false in the config turns this off.
Lost on the way home ("dock not found"), the robot is guided room by
room along the configured route (waypoints: {home_route: [...]}, or a
room's own home_route), then sent home. goto does the same by hand:
it sends the robot to a floor spot in a room, or to a point, outside the
no-go zones. Like Stop and Home, it ends the current job.
The web page
opentapovac serve also serves a web page for day-to-day runs: buttons
for the rooms and the presets, the mode, Stop and "Return to dock", the
status, the event log, questions to answer, and the map. Tapping the map
sends the robot there. The map shows the tracks of the last 12 hours,
mopping in its own colour, with checkboxes for vacuuming, mopping and
movement. The version is shown next to the title.
The robot keeps only a short track and clears it now and then, so the
daemon fetches it while a run goes on, also for runs started from the
app, and keeps it in ~/.local/state/opentapovac/tracks/, with the robot's
own clean records (time, area, mop washes) in clean-records.jsonl next
to it. The map shows the recorded tracks of the last 12 hours
(opentapovac map --max-age HOURS).
The daemon binds to localhost. To reach the web page from phones, put a
reverse proxy with authentication in front of it; the daemon has no
accounts of its own. Name the proxy's host in allowed_hosts: in the
config: the daemon answers only to localhost and the names listed there,
and takes POSTs only as JSON from its own origin, so other web pages can't
drive the robot through your browser.
A carry room gets a run of its own, before the others. For a
carry_in room the run starts from the dock as usual (so the mops go on
first), and the job asks someone to carry the robot in when it leaves
the base; the robot waits at the doorstep. A minute after it is put
down the job checks that the robot knows where it is, and stops it if
it thinks it is in another room. "skip" drops the room and goes on with
the rest. For a carry_out room it asks each time the robot heads for
the dock. Lifting the robot and putting it down counts as "done". The question shows on the web page, on the
terminal of a standalone or --wait clean, and in opentapovac status;
the job waits until someone answers or stops it. Meanwhile the robot is
paused, so it doesn't give up the run at the doorstep, and resumed once
carried (pause_for_carry: false turns this off).
Before a run the robot's map must be locked and "auto change map" off,
since a bad relocation can otherwise overwrite the map; --force skips
that check.
Are you using this? Please say so
The maintainer's interest in this project will likely die the day the robot breaks down. If you use OpenTapoVac, or would like to, please open an issue or a discussion and say which robot you have. Knowing there are other users matters, and at some point someone else may need to take over as maintainer.
License
AGPL-3.0-or-later. See LICENSE.
Metadata
Release files for opentapovac 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| opentapovac-0.1.0.tar.gz | 176.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| opentapovac-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 274.1 kB
Release files / opentapovac-0.1.0.tar.gz
| Download URL | opentapovac-0.1.0.tar.gz |
|---|---|
| Size | 176.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2524003d9497e124e737f58045a8f908ed1a7c434ca2ee227f9059e7c4f3dc53
|
|
BLAKE2b-256 checksum How to use checksums |
06a2ecf7199c50246a61dd96bf1053c6b8cbe717f53d0410ef7e3baf4875a001
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / opentapovac-0.1.0-py3-none-any.whl
| Download URL | opentapovac-0.1.0-py3-none-any.whl |
|---|---|
| Size | 97.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fcf4e4a83fef47c2c82ab3dad465320cc11b223d4a27f3d9f9456c5b3cea212b
|
|
BLAKE2b-256 checksum How to use checksums |
a3f93e0f13992a62ea57f95064efcc151634542b92a839551f847248610fd407
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log