Roomba i7+ (Rest980) with Selective Room Cleaning - Integrate your iRobot Roomba with Home Assistant

Hi @ia74 . I have tried following the steps and have been able to partly add the integration. What I mean is that, the integration is added but with a Failed setup where is says “Error Communicating with API”. I have got the same result for both Roomba S9+ and Roomba Combo 10 Max (I have 2 Roombas and 1 Braava M6). Would appreciate any guidance on what I can do from here as I am stuck. Thanks

Hello everyone, I integrated my Roomba i7+ using rest980 and I can see all the entities. I had two entities called “unnamed room” that I disabled because they didn’t belong to any of the rooms I have.

I tried selecting One Pass on a couple of rooms and starting the vacuum, but after exactly 6 minutes—when in the app it shows “discovering”—the robot returns to the base and I get a localization error. Am I doing something wrong?

UPDATE:
Maybe I found the problem, but I don’t know how to solve it:

I have two maps because I have two houses.
In the attributes of vacuum.robotname, pmap0_id is equal to the map ID of the other house, and I have no way to force it to use the correct one.

Any ideas?

Hey there! Sorry, this is unfortunately a known issue. I’m working on a fix that won’t have too much collateral damage, as it has to do with unique ID.

Hey! You know, this isn’t a scenario I had in mind, but this is definitely an easier fix from my end [than the multiple roomba bug]. Sorryabout that! It happens because starting a job defaults on Roomba’s default pmap0 ID for every room.

Hi, I’m happy that it can be easily resolved.
Looking forward to the update :slight_smile:

Hey all, sorry for the wait!

v1.18.0 is out now and fixes the issue with multiple maps selecting the wrong room for the wrong map ID. It’s a simple fix; for now, you can only execute cleaning rooms in one map per run (as of right now). I’ll look into multiple selections across maps, but I’m not sure how that’ll look.

As for the multiple Roomba situation, the reason why that’s taking so long has to do with the way roomba_rest980 handles unique IDs. The way it works now is: HA selects a unique ID, which should be per Roomba but is instead per server, by MD5 hashing your local server URL. This will be fixed with a config flow version upgrade (automatic migration, this time it will be based on BLID.)

Hello.

Sorry for dumb questions, but I just want to make sure I got it right.

You mentioned you’re updating dorita/rest980.

Is it this one

GitHub - ia74/roomba_rest980: Integrate Home Assistant with your iRobot Roomba using rest980. ?

I am using the old one, so I need to double check.

The Docker image is koalazak/rest980:latest, right?

Would all of this work with cloud features disabled?

I’m asking because in GitHub - ia74/roomba_rest980: Integrate Home Assistant with your iRobot Roomba using rest980. you say

“You must check “Enable cloud features?” for the cloud API to be used.”

And after irobot filing for bancrupcy and being overtaken by some Chinese company, I don’t feel ok with using cloud anymore.

Would I miss selective room clearing and other features? I do know room ids and I’m fine using those.

Thank you very much for all the work.

No dumb questions here!

  • Yes, correct, the docker image is by koalazak.
  • It does work without cloud features; they’re all supplemental. You wouldn’t miss selective room cleaning, as you can re-implement it with the start API/event (there will be a guide to do so in the future, but you can also read the start implementation).
    • Without Cloud, pressing Start on HA’s native entity just starts a clean everywhere/default routine.

Thanks for giving my integration a try!

Thank you.

How substantial was the recent change to rest980 image? I noticed it grew by some 100-200 MB.

How can I read start implementation? Sorry, I’m rather new to this.

Would something like this work?

curl -X POST http://192.168.60.139:3011/api/local
-H “Content-Type: application/json”
-d ‘{“command”:“start”}’

I tried to follow this:

But it failed utterly. I suppose because he used cloud and he also implemented 2pass vacuuming on his own before you did.

Yes, except you should POST this to /api/local/action/cleanRoom

{
  "ordered": 1,
  "pmap_id": "ID",
  "regions": [
    {
      "region_id": 0,
      "type": "rid",
      "params": {"noAutoPasses": False, "twoPass": False}
    },
  ]
}

Where for zones it should be “zid” and i think “zone_id” if i haven’t forgot

Hey all!

HA is adding room-based vacuum support natively in 2026.3.3!
Naturally, my integration will be updated as soon as possible (once HA updates too & docs are provided) to support this feature!

Alongside this, I’ll look into adding support for MatterHub too!

And maybe add some more local and easier to read guides.

However! I am currently busier than usual as of right now, so these features will take a little longer, as I aim for quality.
Thank you all for the support! Feel free to open issues, they’re helpful as I only own an i7.

3 Likes

Do you plan to provide some examples of use?
I use HA only for a while and this is for me quite a challenge to put together.

With the help of AI I hallucinated this roomba.yaml and lovelace card.

# Roomba custom cleaning package
# /config/automations/roomba.yaml

sensor:
  - platform: rest
    name: roomba_state_raw
    resource: "http://192.168.60.139:3011/api/local/info/state"
    method: GET
    scan_interval: 60
    json_attributes:
      - pmaps

template:
  - sensor:
      - name: roomba_user_pmapv_id
        state: >
          {% set p = state_attr('sensor.roomba_state_raw','pmaps') %}
          {% if p %}
            {{ p[0]['VabtFPvZSveRlmdU2K1n4Q'] }}
          {% else %}
            unavailable
          {% endif %}

rest_command:
  roomba_clean_custom:
    url: "http://192.168.60.139:3011/api/local/action/cleanRoom"
    method: POST
    content_type: "application/json"
    payload: "{{ payload }}"

input_select:
  vacuum_room_kitchen:
    name: Kitchen
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:chef-hat

  vacuum_room_diningroom:
    name: Dining Room
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:table-chair

  vacuum_room_livingroom:
    name: Living Room
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:sofa-outline

  vacuum_room_hallway:
    name: Hallway
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:shoe-print

  vacuum_room_bedroom:
    name: Bedroom
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:bed-outline

  vacuum_room_childsroom:
    name: Child Room
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:teddy-bear

  vacuum_room_bathroom:
    name: Bathroom
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:shower-head

  vacuum_room_eating_area:
    name: Eating Area
    options: ["off", "1x", "2x"]
    initial: "off"
    icon: mdi:cake-variant-outline

script:
  roomba_cycle_room:
    mode: parallel
    fields:
      entity:
        description: "input_select room entityi"
    sequence:
      - variables:
          current: "{{ states(entity) }}"
      - choose:
          - conditions: "{{ current == 'off' }}"
            sequence:
              - service: input_select.select_option
                target: { entity_id: "{{ entity }}" }
                data: { option: "1x" }
          - conditions: "{{ current == '1x' }}"
            sequence:
              - service: input_select.select_option
                target: { entity_id: "{{ entity }}" }
                data: { option: "2x" }
          - conditions: "{{ current == '2x' }}"
            sequence:
              - service: input_select.select_option
                target: { entity_id: "{{ entity }}" }
                data: { option: "off" }

  roomba_start_selected_rooms:
    mode: single
    sequence:
      - variables:
          room_map:
            vacuum_room_kitchen:     { id: 7,  type: "rid" }
            vacuum_room_diningroom:  { id: 1,  type: "rid" }
            vacuum_room_livingroom:  { id: 10, type: "rid" }
            vacuum_room_hallway:     { id: 8,  type: "rid" }
            vacuum_room_bedroom:     { id: 4,  type: "rid" }
            vacuum_room_childsroom:  { id: 3,  type: "rid" }
            vacuum_room_bathroom:    { id: 9,  type: "rid" }
            vacuum_room_eating_area: { id: 0,  type: "zid" }

          regions_list: >
            {% set ns = namespace(regions=[]) %}
            {% for entity, data in room_map.items() %}
              {% set value = states('input_select.' ~ entity) %}
              {% if value in ['1x', '2x'] %}
                {% set two_pass = (value == '2x') %}
                {% if data.type == 'zid' %}
                  {% set region = {
                    'zone_id': data.id | int,
                    'type': data.type,
                    'params': {'noAutoPasses': true, 'twoPass': two_pass}
                  } %}
                {% else %}
                  {% set region = {
                    'region_id': data.id | int,
                    'type': data.type,
                    'params': {'noAutoPasses': true, 'twoPass': two_pass}
                  } %}
                {% endif %}
                {% set ns.regions = ns.regions + [region] %}
              {% endif %}
            {% endfor %}
            {{ ns.regions }}

          ordered_flag: >
            {{ 1 if regions_list | length > 1 else 0 }}

          clean_payload: >
            {{ {
              'ordered': ordered_flag,
              'pmap_id': 'VabtFPvZSveRlmdU2K1n4Q',
              'user_pmapv_id': states('sensor.roomba_user_pmapv_id'),
              'regions': regions_list
            } | to_json }}

      - condition: template
        value_template: "{{ regions_list | length > 0 }}"

      # --- DEBUG ---
      - service: persistent_notification.create
        data:
          title: "Roomba debug payload"
          message: "{{ clean_payload }}"
      # ------------------------------------------------------------

      - service: rest_command.roomba_clean_custom
        data:
          payload: "{{ clean_payload }}"

      - service: input_select.select_option
        target:
          entity_id: >
            {{ room_map.keys() | map('regex_replace', '^', 'input_select.') | list }}
        data:
          option: "off"

Lovelace card:

type: vertical-stack
cards:
  - type: custom:roomba-vacuum-card
    entity: vacuum.robik
    battery_entity: sensor.robik_battery
    clean_base: false
  - type: grid
    columns: 2
    square: false
    cards:
      - type: tile
        entity: input_select.vacuum_room_kitchen
        name: Kitchen
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_kitchen
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_kitchen', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_kitchen', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_diningroom
        name: Dining Room
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_diningroom
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_diningroom', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_diningroom', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_eating_area
        name: Eating Area
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_eating_area
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_eating_area', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_eating_area', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_livingroom
        name: Livingroom
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_livingroom
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_livingroom', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_livingroom', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_hallway
        name: Hallway
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_hallway
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_hallway', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_hallway', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_bedroom
        name: Bedroom
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_bedroom
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_bedroom', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_bedroom', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_childsroom
        name: Child Room
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_childsroom
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_childsroom', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_childsroom', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
      - type: tile
        entity: input_select.vacuum_room_bathroom
        name: Bathroom
        tap_action:
          action: call-service
          service: script.roomba_cycle_room
          data:
            entity: input_select.vacuum_room_bathroom
        card_mod:
          style: |
            :host {
              {% if is_state('input_select.vacuum_room_bathroom', '1x') %}
                --tile-color: var(--warning-color);
              {% elif is_state('input_select.vacuum_room_bathroom', '2x') %}
                --tile-color: var(--error-color);
              {% else %}
                --tile-color: var(--state-inactive-color);
              {% endif %}
            }
  - type: button
    name: Start Vacuuming
    icon: mdi:play-circle
    tap_action:
      action: call-service
      service: script.roomba_start_selected_rooms
    visibility:
      - condition: or
        conditions:
          - condition: state
            entity: input_select.vacuum_room_kitchen
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_diningroom
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_eating_area
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_livingroom
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_hallway
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_bedroom
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_childsroom
            state_not: "off"
          - condition: state
            entity: input_select.vacuum_room_bathroom
            state_not: "off"

It looks nice, but doesn’t work at all and I fail to recognize valid code from halucination.

I just want to be able to select rooms as I like and let roomba vacuum. That’s it.