Vacuum cleaner has jobs to be done

After I gained root access to my vacuum cleaner robot, a new world of opportunities emerges. This is the great aspect of owning your own home devices. And it is geometric. After you add a new liberated device to your home mesh, you can start thinking on all the combinations among your other devices, even if they won’t make any sense. Vacuum cleaner + thermostat? So you clean the room when it reaches some temperature? It doesn’t seem to go anywhere. Vacuum cleaner + roller shutters, so you clean after you close them? Neither it does. Vacuum cleaner + mobile presence? Hmm.. that might make sense!

But beyond the solutions looking for a problem, I am really committed to find real, practical use cases. And I found 2 great cases for squeezing the new liberated capabilities:

  • Starting house cleanup after we leave the house. It takes some time for the robot to clean the house. And you want it to do it while you are not at home. But not every time you leave, it depends on the last time you cleaned it, how long you are going to be out… So you want to tell the robot, Hey! We are leaving the house shortly! When we leave, start cleaning the house!
  • Choosing what rooms to clean. This fits in my house cleaning routine, usually on the weekend. I start organizing rooms, making the beds, cleaning the dust… After I finish one room, I want to send the robot to that room. If I finish the next room, I want to queue it after the other. This capability is not available in the robot nor in the app. You can tell the robot to clean several rooms at once, but not queuing rooms on the fly

Controlling the robot

After you root the robot, there are several alternatives to control it:

  • python-miio. A command line tool for communicating with Xiaomi devices. It supports many different gadgets, from light bulbs to air purifiers, and vacuum cleaners. You need to get an authentication token, which can be be obtained from the robot if you have rooted it, or from the android app. In the case of It doesn’t work very well with the Viomi V7, since it doesn’t get track of the sequence id. You need to reboot the robot to use the command line tool again
  • OpenHAB Xiaomi Mi IO Binding There is one Xiaomi addon for OpenHAB, which works pretty well. It also needs the authentication token to communicate with the robot. The basic commands are already available, and you can always send custom commands, which people have extracted from the binaries. The problem with this option is the robot state availability. It does not work in a push way, from the robot to OpenHAB. You need to configure the interval in which OpenHAB will ask to robot, and will update the OpenHAB things with the new values. This is not very practical to implement real time things, like updating cleaning rooms queue.
  • Valetudo Valetudo provides 2 more ways to communicate with the robot
    • REST API. Valetudo exposes a complete Swagger enabled API documented API with all the capabilities of the robot
    • MQTT You can also configure a MQTT server, and Valuetudo will publish all the robot information to the MQTT server. It will also listen for commands. This is great for push notifications, you can subscribe to MQTT topics, instead of pulling the information from the robot. Valetudo will publish robot states in real time

Implementation

With these tools, I implemented the 2 functionalities I described before in my kitchen dashboard

Cleanup after we leave the house

I added a new button in the dashboard. When you push it, it just publishes a true value on a MQTT topic. A rule in OpenHAB will check this value when our presence group turns off. That is, we left the house. If this value is set, it will tell the the robot to start cleaning the house

rule "start when leaving"
when
  Item our_presence changed to OFF
then
  if (robotito_start_when_leaving.state == ON) {
    robotito_commands.sendCommand("set_mode_withroom [0, 1, 0]")
    robotito_start_when_leaving.sendCommand(OFF)
  }
end

House map

I implemented a map of my house in the kitchen dashboard. This way, I can just touch the rooms in the map to send the robot to clean them. There is a MQTT channel for this, pending_rooms, where a comma-separated list of rooms ids gathers the rooms that need to be cleaned

A set of rules in OpenHAB watches for the changes in both, the robot state (via Valetudo MQTT) and the pending_rooms channel. The set of rules allows to enqueue rooms, remove rooms in the middle of the clean, or canceling and send the robot to the dock.

In order to build the set of rooms, I needed to draw the states of the robot. This was very useful to program the rules later

Flowchart after pending_rooms changes
Flowchart after robot state changes

I programmed the rules using the native OpenHAB rules DSL. It is based in Xtend. It was a real mess to program the rules. I hate Xtend. I find it a real nightmare. It is very limited. It took me several hours just to figure out how to do an Array unshift. I tried to install JS or Python rules engines, but it required to many additional steps

Anyway, I manage to get the functionality with the following rules file

// Commands
var stop = "set_mode [0]"
var home = "set_charge [1]"

var currentRoom = ''
var willClean = false

rule "pendingrooms changed"
when
  Item robotito_pendingrooms changed
then
  var rooms = robotito_pendingrooms.state.toString
  logError("robotito", "robotito_pendingrooms changed to: " + rooms)
  var state = valetudo_robotito_status.state.toString

  if (state == "returning") {
    if (rooms == "") {
      logError("robotito", "state is returning and no pending rooms. pass")
    } else {
      logError("robotito", "state is returning and there are pending rooms. stopping")

      robotito_commands.sendCommand(stop)
    }
    
    return;
  }

  if (state == "idle" || state == "docked" || state == "stop") {
    if (rooms == "") {
      logError("robotito", "rooms are empty. pass")

      return;
    }
    
    var firstRoom = rooms.split(",").get(0)

    logError("robotito", "state was: " + state + ". cleaning first pending room: " + firstRoom)

    currentRoom = firstRoom
    
    robotito_commands.sendCommand("set_mode_withroom [0, 1, 1, " + currentRoom + "]")

    return;
  }

  if (state == "cleaning") {
    var list = rooms.split(",")
    var firstRoom = list.get(0)

    if (firstRoom == currentRoom) {
      logError("robotito", "state was cleaning and firstRoom is currentRoom: " + currentRoom + ". pass")
      return;
    }

    if (rooms == "") {
      logError("robotito", "state was cleaning and firstRoom is empty. sending home")

      currentRoom = ""

      robotito_commands.sendCommand(home)
    } else {
      logError("robotito", "state was cleaning and firstRoom is not empty nor currentRoom. stopping to change room")

      currentRoom = ""

      robotito_commands.sendCommand(stop)
    }
   return;
  }

  logError("robotito", "unknown robotito state: " + state)
end

rule "state changed"
when
  Item valetudo_robotito_status changed
then
  var state = valetudo_robotito_status.state.toString
  var rooms = robotito_pendingrooms.state.toString

  logError("smarthome.event", "robotito state changed to " + state)


  if (state == "cleaning") {
    willClean = false
    logError("robotito", "robotito state changed to cleaning. pass")
   
    return;
  }

  if (state == "returning") {
    if (willClean) {
      logError("robotito", "state changed to returning, but will clean. pass")
      willClean = false
      return;
    }

    if (rooms == "") {
      logError("robotito", "robotito state changed to returning, and no rooms pending. pass")
      
      return;
    }

    if (rooms.split(",").size() < 2) {
      logError("robotito", "robotito state changed to returning, and room " + rooms + " was just cleaned. reseting pending rooms and pass")

      currentRoom = ""
      robotito_pendingrooms.sendCommand("")

      return;
    }

    logError("robotito", "robotito state changed to returning, and there are pending rooms: " + rooms + " stopping")

    robotito_commands.sendCommand(stop)
  
    return;
  }

  if (state == "idle") {
    if (rooms == "") {
      logError("robotito", "robotito state changed to idle, and no rooms pending. pass")
    } else {
      willClean = true
      logError("robotito", "willClean set to true")

      if (currentRoom == "") {
        var firstRoom = rooms.split(",").get(0)

        logError("robotito", "state was idle, but currentRoom was empty. start cleaning first pending room: " + firstRoom)

        currentRoom = firstRoom
    
        robotito_commands.sendCommand("set_mode_withroom [0, 1, 1, " + currentRoom + "]")
      } else {
        logError("robotito", "robotito state changed to idle, there was currentRoom: " + currentRoom + " and there are pending rooms: " + rooms + ". jumping to next room")
        var list = rooms.split(",")
        var newPendingRooms = ""
        for (var i = 1; i < list.size(); i++) {
          newPendingRooms = newPendingRooms + list.get(i)

          logError("robotito", "i: " + i)
          logError("robotito", "setting newPendingRooms: " + newPendingRooms)
          if (i != list.size() - 1) {
            newPendingRooms = newPendingRooms + ","
          }
        }
  
        robotito_pendingrooms.sendCommand(newPendingRooms)
      }
    }

    return;
  }

  logError("robotito", "robotito state changed to " + state + ". not addressed")
end

These two functionalities are pretty handy and show up the benefits of controlling your things. Besides the peace of knowing what it is going on inside your own devices.

I do have root access to my vacuum cleaner

When I started this IoT journey, my goal was using always free open source hardware and software. At least, as much as I could, given the available possibilities.

The next stage in the journey was the vacuum cleaner

There is clearly a challenge in this stage: would I find free/open source hardware in the vacuum cleaner world? There are some projects of DIY hardware for vacuum cleaners. But they seem to be in very early stages. I wanted something more finished and production ready

Valetudo

Luckily, like with my former experiences with the thermostat or the thermometer, I found an amazing project that frees your robot from the cloud: Valetudo

When I discovered Valetudo several months ago, it was in a very early stage. It is amazing what this project has achieved within this year, as you can see in this release: Recap 0.6.1 to 2021.11.0. Valetudo is now a real alternative to managing robots without the proprietary clouds. It is pretty usable with most of the features you will expect from a modern robot: home map, zone and room cleaning, manual move, timers. And of course, the required capabilities for your home hub: MQTT support, a REST API, OpenHAB and Home Assistant integration. And more.

image
Valetudo screenshot from 2021.11.0 release

Valetudo runs in several vacuum cleaner models and brands. That was my only issue to consider when we bought the new vacuum cleaner: the hackability of the robot. At the end of 2020, my choice was a Viomi STYTJ02YM. However, they seem to be changes in recent Viomi V8 products that make rooting harder. If I had to choose a model today, I would follow the Valetudo section Which robot should I buy in 2021 to use it with Valetudo?

Rooting the robot

The first step to gain control of the robot was obtaining root access. Luckily, there is this awesome blog post from rumpeltux, Rooting the Xiaomi STYJ02YM (viomi-v7) Vacuum Robot where the rooting process is explained to the detail. There is even a shell script to automatize the rooting process. In a nutshell, the process consists on:

  • connect the computer to the robot USB, under the battery
  • fixing the adb shell, which is killed shortly after the robot is booted
  • install ssh
  • install Valetudo

The process was easy in my case. I only found an issue USB cable, and it was because I was using a USB cable without data capabilities. Beware of cables that are change only!

The vacuum cleaner USB terminal

Once you get root access to the robot, you can see it uses Tina Linux, a distribution based on OpenWrt. So there is plenty of documentation on how it works

But the last dilemma was about configuring the WiFi. This is usually achieved using the official Xiaomi app, which also connects to Xiaomi’s servers in the cloud. The alternative was configuring the WiFi in OpenWrt. However, I could see there were also other Viomi’s files involved in the WiFi configuration. I didn’t want to mess things up. so I installed the Xiaomi app and configured the WiFi. I kept the app installed, but I will uninstall it. I never used it again. When installing Valetudo, you setup the robot to talk to you localhost instead of the Xiaomi’s cloud. No more communication with Xiaomi’s servers anymore!

So this is not open source hardware. But we have a liberated device from the proprietary cloud, running free open source software with lots of possibilities. Next step will be connecting to the home hub and the kitchen dashboard!