The semi-automatic robot vacuum

Posted Jul 17, 2023

I was recently given a Roborock S7 robot vacuum cleaner. It’s an indulgence, really. We’ve got 78 m2 of floor space so automated vacuuming really isn’t necessary. But for the first time in years, I’m living in a place that uses one continuous floor with no steps between rooms, which is the ideal condition for such a device. We’ve nicknamed it Rocky after it started — and lost — a fight with our clothes drying rack.

Rocky meeting his match

As previously noted, I use Home Assistant to tie my smart home stuff together. When I got Rocky, the only way to connect it to Home Assistant was through the Xiaomi Miio integration, which required using the Mi Home app rather than the Roborock one. The 2023.5 release of Home Assistant introduced a dedicated Roborock integration which is… mediocre. It actually seems to have a lot fewer features than the old one, so switching was a bit underwhelming. But they’re promising more features down the road, and I suppose there’s now a mop intensity dropdown available.

Of course, you don’t integrate with Home Assistant for manual control; it’s the potential for automation that makes it worthwhile. My dream was to make Rocky totally autonomous: I would have it clean the entire apartment every few days when no one was home, requiring no effort on my part. It would be a real home of the future. It’s technically possible to do, but the reality is that we’re giant slobs who leave our clothes all over the bedroom floor, and despite his willingness to try, Rocky can’t actually swallow a pair of jeans. Oh, and I guess the dustbin needs to be emptied once in a while, as I’m too much of a cheapskate to get the self-emptying dock. Yes, there’s a limit to my indulgences.

So I have to actually know when the thing will run so I can prepare the place. What I really needed was a semi-automatic automation that could start cleaning when no one is home, but required a manual go-ahead first. And it should allow skipping some rooms to avoid another altercation with the drying rack. With the current state of the Roborock integration, getting this in order was a multi-step process that involved a heavily templated script and some helper entities. I’m gonna explain how I did it here.

Helper entities

First, I set up a number of boolean helpers that control which rooms should be included when cleaning, as well as an Auto clean boolean that is used as a condition for the automation to run (which we’ll get to). It looks like this on my Lovelace dashboard:

Boolean helpers in Lovelace

Selective room cleaning script

The helpers are used in a script that sends a “start cleaning” command to the Roborock integration. This command is either a “selective room cleaning” command if any of the room toggles are on, or a general “clean everything” command otherwise. Selective room cleaning requires figuring out technical identifiers for each room, which is currently an elaborate process, so that’s the first step.

The following script uses an unsightly mix of YAML and Jinja syntax to transform the states of the room booleans into a list of Roborock room identifiers for the app_segment_clean command. I’m abusing the Jinja templating to write out a string that happens to be parseable a YAML list, by concatenating comma-separated numbers and then chopping off the final comma.

alias: Selective room cleaning
sequence:
  - if:
      - condition: or
        conditions:
          - condition: state
            entity_id: input_boolean.clean_office
            state: "on"
          - condition: state
            entity_id: input_boolean.clean_living_room
            state: "on"

          # ... other rooms omitted ... #
    then:
      - service: vacuum.send_command
        data:
          command: app_segment_clean
          params:
            - segments: |
                {%- set rooms = "" -%}
                {%- if is_state('input_boolean.clean_office', 'on') -%}  
                  {%- set rooms = rooms + "16," -%}
                {%- endif -%}
                {%- if is_state('input_boolean.clean_living_room', 'on') -%}
                  {%- set rooms = rooms + "17," -%}
                {%- endif -%}

                # ... other rooms omitted ... #

                [ {{ rooms[:-1] }} ]
            - repeat: 1
        target:
          device_id: 9b283511d92e17bf340dbeb97fc90d3e
    else:
      - device_id: 9b283511d92e17bf340dbeb97fc90d3e
        domain: vacuum
        entity_id: vacuum.rocky
        type: clean
mode: single

Automating script execution

The final step is invoking the script from an automation. It also turns off the helper so it won’t run again without manual intervention. I’ve currently set it to run when all of the following are true:

  • The home has been vacant for 10 minutes
  • The Auto clean helper is on
  • Rocky is docked
alias: "Away for 10 minutes: start cleaning"
trigger:
  - platform: numeric_state
    entity_id: zone.home
    below: 1
    for:
      hours: 0
      minutes: 10
      seconds: 0
condition:
  - condition: state
    entity_id: input_boolean.auto_clean
    state: "on"
    enabled: true
  - condition: device
    device_id: 9b283511d92e17bf340dbeb97fc90d3e
    domain: vacuum
    entity_id: vacuum.rocky
    type: is_docked
action:
  - service: script.turn_on
    data: {}
    target:
      entity_id: script.selective_room_cleaning
    enabled: true
  - service: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.auto_clean
    enabled: true
mode: single

With all this, I can set Rocky up for success by moving stuff off the floor and turning on Auto clean, which starts cleaning once everyone has left. Is this too much work for something like this? Yes. But overcomplicating is kinda my thing.