Xiaomi Cloud Vacuum Map Extractor

Often a simple HA restart helps and the map shows up thereafter. See post #494.

You can also check out these issues: #157, #180

I think you are right!
After several cleanups rooms appeared:

Earlier there were no rooms:

And the most important thing - I do not observe misalignment so far!

Here are some rules to provide same mutual alignment between a floorplan & a vacuum’s map:

  1. Do not shift a charging dock.
  2. Enable “Save map” feature (post).
  3. Do not start cleaning by a vacuum’s “hardware” button.
  4. Do not start cleaning by using a vacuum.start service.
  5. Always start cleaning by using a “zone cleaning” (use this card) or a “segment cleaning” feature by calling this service:
service: vacuum.send_command
service_data:
  entity_id: '[[SENSOR_VACUUM]]'
  command: app_segment_clean
  params: [2, 3, 16, 5, 17]

Room numbers may be retrieved from a vacuum’s map attributes.

Piotr, thank you again for the job!

@ 3_14
Piotr, may be you know:
is it possible to use a template for the params option?

service: vacuum.send_command
service_data:
  entity_id: '[[SENSOR_VACUUM]]'
  command: app_segment_clean
  params: [2, 3, 16, 5, 17]

I would like to generate the params option dynamically dependingly on which rooms are selected for cleaning & how many passes are selected for each room.
I know that there is fantastic Vacuum Interactive Map Card - but so far I cannot integrate it to my floorplan and wanted to select rooms from UI.

Where do you want to achieve it? It should be possible to do in a script

To achieve it:

  1. Create 3 set of entities:
  • input_boolean for each room - whether to add the room to the “cleaning list” or not;
  • input_number for each room - for keeping / changing room numbers (taken from camera);
  • input_number for each room - for keeping counts of passes (1…3, for example).
  1. Create a group containing these input_boolean entities.

  2. Create template sensor:

sensor:
  - platform: template
    sensors:
      vacuum_clean_command:
        value_template: >-
          {% set ns = namespace(COMMAND = "") -%}
          {%- set ROOM_FLAGS = expand('group.vacuum_clean_rooms') -%}

          {%- for flag in ROOM_FLAGS -%}
            {%- if flag.entity_id | regex_match("input_boolean.vacuum_clean_room_", ignorecase=False) and
                   is_state(flag.entity_id,'on') -%}
              {%- set ROOM = flag.entity_id.split("vacuum_clean_room_")[1] -%}
              {%- set ROOM_NUMBER = states('input_number.vacuum_clean_room_number_' + ROOM)|int -%}
              {%- set CLEAN_COUNT = states('input_number.vacuum_clean_room_count_' + ROOM)|int -%}
              {%- set ROOM_NUMBER_STRING = (ROOM_NUMBER|string + ",") * CLEAN_COUNT -%}
              {%- set ns.COMMAND = ns.COMMAND + ROOM_NUMBER_STRING -%}
            {%- endif -%}
          {%- endfor -%}

          {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
          [{{ ns.COMMAND }}]

where:

  • input_boolean.vacuum_clean_room_* - names for input_boolean entities;
  • input_number.vacuum_clean_room_number_* - names for room numbers;
  • input_number.vacuum_clean_room_count_* - names for counts;
  • group.vacuum_clean_rooms - name of the group.
  1. Create smth like this:
    image
    You make hide this card inside fold-entity-row and expand it when needed.

So, the question is - is it possible to use smth like this:

service: vacuum.send_command
service_data:
  entity_id: '[[SENSOR_VACUUM]]'
  command: app_segment_clean
  params: "{{ states('sensor.vacuum_clean_command') }}"

Yeah, it should be possible to do in a script:

vacuum_rooms:
  sequence:
    - service: vacuum.send_command
      data:
        entity_id: vacuum.xiaomi
        command: app_segment_clean
        params:  "{{ states('sensor.vacuum_clean_command') }}"

or without template sensor:

vacuum_rooms:
  sequence:
    - service: vacuum.send_command
      data:
        entity_id: vacuum.xiaomi
        command: app_segment_clean
        params:  >-
          {% set ns = namespace(COMMAND = "") -%}
          {%- set ROOM_FLAGS = expand('group.vacuum_clean_rooms') -%}

          {%- for flag in ROOM_FLAGS -%}
            {%- if flag.entity_id | regex_match("input_boolean.vacuum_clean_room_", ignorecase=False) and
                   is_state(flag.entity_id,'on') -%}
              {%- set ROOM = flag.entity_id.split("vacuum_clean_room_")[1] -%}
              {%- set ROOM_NUMBER = states('input_number.vacuum_clean_room_number_' + ROOM)|int -%}
              {%- set CLEAN_COUNT = states('input_number.vacuum_clean_room_count_' + ROOM)|int -%}
              {%- set ROOM_NUMBER_STRING = (ROOM_NUMBER|string + ",") * CLEAN_COUNT -%}
              {%- set ns.COMMAND = ns.COMMAND + ROOM_NUMBER_STRING -%}
            {%- endif -%}
          {%- endfor -%}

          {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
          [{{ ns.COMMAND }}]
1 Like

Great, will try it next day! Thank you!

@3_14
Piotr, is it possible to support templates for yaml config file?
I think that it would be great to switch on/off displaying rooms etc…

maybe someday :wink:

1 Like

Piotr, I got some questions about your script, could you clarify?
I made a small chart of the script:

1) python_script.vacuum_send_command
   send first 5 segments

if command == 'app_zoned_clean':
     if `there more than 5 segments to clean`
          2) Wait until `vacuum = CLEANING`
          3) Wait until `vacuum != CLEANING`  (waiting for completion of cleaning the first 5 segments)
          4) Pause the vacuum
          5) script.vacuum_send_command_multiple_zones (recursive call of the same script)
             send rest of the segments
     endif
endif

Questions:

  1. What is a format of the params variable? Should it be like
[1, 2, 3, 45, 67, 8]

I see that you provided some checks & parsing & cleanup to ensure that the input data format is valid.

  1. Regarding the python_script.vacuum_send_command:
    According to the script, you are passing first 5 segments to the vacuum - does it mean that the vacuum can process max 5 segments per service call?

  2. According to the py script definition, that params value (i.e. first 5 segments) will be converted to

['1', '2', '3', '45', '67']

before sending to the vacuum. Does it mean that a vacuum accepts this format only?

  1. I think this line is not required for the first call:
    {%- set rest = cleaned | replace(firstBatch, "") | replace("[,[", "[[") -%}

  2. I think that the firstBatch and rest may be defined as variables. Just a speculation, I am still learning and never used variables in scripts so far…

Well, the script has been created over a year ago and some stuff has changed in HA . Right now it should be possible to make this script simpler - I think usage of python script can be avoided.

Answers:

  1. Yes
  2. Yes, as far as I know. You can try to check it in Xiaomi Home (my vacuum doesn’t have rooms)
  3. Actually it should be [1, 2, 3, 45, 67]
  4. Yes, it isn’t. I have left it there for consistency reasons
  5. Yes, the script has been created before variables in script appeared in HA :wink:

Thank you very much for your answers!
Now - about my problem.

I described my approach to generate a command for cleaning segments.
The template sensor with this code

          {% set ns = namespace(COMMAND = "") -%}
          {%- set ROOM_FLAGS = expand('group.vacuum_clean_rooms') -%}

          {%- for flag in ROOM_FLAGS -%}
            {%- if flag.entity_id | regex_match("input_boolean.vacuum_clean_room_", ignorecase=False) and
                   is_state(flag.entity_id,'on') -%}
              {%- set ROOM = flag.entity_id.split("vacuum_clean_room_")[1] -%}
              {%- set ROOM_NUMBER = states('input_number.vacuum_clean_room_number_' + ROOM)|int -%}
              {%- set CLEAN_COUNT = states('input_number.vacuum_clean_room_count_' + ROOM)|int -%}
              {%- set ROOM_NUMBER_STRING = (ROOM_NUMBER|string + ",") * CLEAN_COUNT -%}
              {%- set ns.COMMAND = ns.COMMAND + ROOM_NUMBER_STRING -%}
            {%- endif -%}
          {%- endfor -%}

          {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
          {{ ns.COMMAND }}

generates this output:
image
Note the “Result type: list” text.
But using this script

script:
  vacuum_clean_rooms:
    alias: 'vacuum: Clean rooms'
    sequence:
      - service: vacuum.send_command
        data:
          entity_id: vacuum.xiaomi_roborock_s50
          command: app_segment_clean
          params: "{{states('sensor.vacuum_clean_command')}}"

gives an error:
Unable to send command to the vacuum: {'code': -10000, 'message': 'data for segment is not a number'}
Tried to discuss this issue, no solution so far.
Using the code directly in script (w/o that template sensor like you proposed before) does not help…

I started learning your script to find answers, not succeeded so far.

Can you try using service xiaomi_miio.vacuum_clean_segment?

service: xiaomi_miio.vacuum_clean_segment
data:
  entity_id: vacuum.xiaomi_roborock_s50
  segments: "{{states('sensor.vacuum_clean_command')}}"

Error message:
Failed to call service xiaomi_miio.vacuum_clean_segment. expected int for dictionary value @ data['segments']. Got None
image

:confused: it seems like a bug in HA template engine… So probably the only way is to launch it via python script

I haven’t played with that for a long time, but last time I checked, the segments were not “templatable”.

There is a service input_select.set_option which accepts a list.
A correct syntax with a template is:

service: input_select.set_options
target:
  entity_id: input_select.test_input_select_templatable
data:
  options: "{{['one','two','three','four']}}"

image
Calling a service for vacuum with the same format

entity_id: vacuum.xiaomi_roborock_s50
command: app_segment_clean
params: "{{['2','3','3']}}"

causes same error
Unable to send command to the vacuum: {'code': -10000, 'message': 'data for segment is not a number'}
I think that the problem is in the service itself - it accepts a number, not a string.
I tried this call:

entity_id: vacuum.xiaomi_roborock_s50
command: app_segment_clean
params: "{{[2|int,3|int,3|int]}}"

it works!!!

1 Like

Soooo, maybe this will work as well?

          ...
          {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
          {{ ns.COMMAND | from_json }}

Made this, gonna test it after completion of the started cleaning:

          params: >-
            {% set ns = namespace(COMMAND = "") -%}
            {%- set ROOM_FLAGS = expand('group.vacuum_clean_rooms') -%}

            {%- for flag in ROOM_FLAGS -%}
              {%- if flag.entity_id | regex_match("input_boolean.vacuum_clean_room_", ignorecase=False) and
                    is_state(flag.entity_id,'on') -%}
                {%- set ROOM = flag.entity_id.split("vacuum_clean_room_")[1] -%}
                {%- set ROOM_NUMBER = states('input_number.vacuum_clean_room_number_' + ROOM)|int -%}
                {%- set CLEAN_COUNT = states('input_number.vacuum_clean_room_count_' + ROOM)|int -%}
                {%- set ROOM_NUMBER_STRING = (ROOM_NUMBER|string +"|int" + ",") * CLEAN_COUNT -%}
                {%- set ns.COMMAND = ns.COMMAND + ROOM_NUMBER_STRING -%}
              {%- endif -%}
            {%- endfor -%}

            {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
            [{{ ns.COMMAND }}]

image

Actually, I have never used from_json things, need some time for testing…


Update:
Because of unknown reasons the script above did not work - same error as before…
So I went back to the previous version with this expression:
[{{ ns.COMMAND }}]
and without this expression:
+"|int"

Code:

  vacuum_clean_rooms:
    alias: 'vacuum: Clean rooms'
    sequence:
      - service: vacuum.send_command
        data:
          entity_id: vacuum.xiaomi_roborock_s50
          command: app_segment_clean
          params: >-
            {% set ns = namespace(COMMAND = "") -%}
            {%- set ROOM_FLAGS = expand('group.vacuum_clean_rooms') -%}
            {%- for flag in ROOM_FLAGS -%}
              {%- if flag.entity_id | regex_match("input_boolean.vacuum_clean_room_", ignorecase=False) and
                    is_state(flag.entity_id,'on') -%}
                {%- set ROOM = flag.entity_id.split("vacuum_clean_room_")[1] -%}
                {%- set ROOM_NUMBER = states('input_number.vacuum_clean_room_number_' + ROOM)|int -%}
                {%- set CLEAN_COUNT = states('input_number.vacuum_clean_room_count_' + ROOM)|int -%}
                {%- set ROOM_NUMBER_STRING = (ROOM_NUMBER|string +",") * CLEAN_COUNT -%}
                {%- set ns.COMMAND = ns.COMMAND + ROOM_NUMBER_STRING -%}
              {%- endif -%}
            {%- endfor -%}
            {%- set ns.COMMAND = (ns.COMMAND)[:-1] -%}
            [{{ ns.COMMAND }}]

Probably earlier I started the script before it was actually updated in HA after correction.
So to be sure I restarted HA - and the script works!

Now everything looks OK:
expanded control panel:
image

expanded settings with restricted access:
image

1 Like