I’ve made a script for Home Assistant which sets up my Vacuum cleaner to mop specific rooms. I want to have another script which can run all the specific room mopping scripts, but in a random order (so that the water in the reservoir will run out at different spots instead of the same place each time).
Unfortunately the shuffle command is not available, and I can’t import random in order to provide it. Am I thinking about this wrong? Is there a more idiomatic way of handling this?
Fisher-Yates! I’ve not heard of that one before, thank you
With a few minor modifications:
{% set services = [ "scripts.mop_kitchen", "scripts.mop_hallway",
"scripts.mop_bedroom", "scripts.mop_living_room"] %}
{% set ns = namespace(x = services) %}
{% for i in range(ns.x | length - 1, 0, -1) %}
{% set j = range(0, i + 1) | random %}
{% if j != i %}
{% set ns.x = ns.x[:j]+[ns.x[i]]+ns.x[j+1:i]+[ns.x[j]]+ns.x[i+1:] %}
{% endif %}
{% endfor %}
alias: Mop Everywhere (Random Order)
sequence: {% for room in ns.x %}
- service: {{ room }}
data: {}{% endfor %}
mode: queued
max: 3
The template produces valid yaml, however when I paste the above into a script (inserting as yaml, naturally), then I get the following error when I attempt to save:
Message malformed: required key not provided @ data['sequence']
I believe you are trying to iterate through the shuffled list but it cannot be written like that.
I know how to sequentially call each script in the shuffled list. However, I have my doubts your robot vacuum cleaner will work the way you expect.
Try this experiment:
Go to Configuration > Scripts
Execute all four mopping scripts in any order.
Don’t wait for the robot to complete a room before executing the next mopping script. Simply execute all four scripts.
What does your robot vacuum do when given four different rooms to do at once? Does it do them in the order they were received or does it simply do the last one it received?
Did you have time to try the experiment I suggested?
My suspicion is that you cannot execute four mopping scripts in rapid succession. I may be wrong but I think you will have to wait for the vacuum to finish cleaning a room before you can execute the next script to clean another room.
Apologies for not getting back sooner - Christmas/New Year happened, and I’ve been unwell (not covid) for a while as well.
So I tried this last night, and exactly what I assume you were expecting to happen happened: that is, the script will skip through each command and exit without waiting for any task to complete.
I have figured out how to make it wait until the cleaning has completed:
However when I try to combine this with the use of templates, the is_state method is interpolated at the point where the template is compiled, and not at the point that the script is evaluated - so the wait will never resolve to true. I’m unsure how I might signal to jinja that the wait_template value should not be compiled yet?
That aside, this is the script I ended up with, and this does indeed wait for each task to complete before moving on to the next one:
Yes, the script I shared below does not have the shuffling behaviour because I could not get it to validate cleanly, but I still want to mop my floor
I shared that script so that it was clear that a resultant YAML such as this would fit requirements, I just need to figure out how to get the shuffling to produce YAML like this.
I hope that’s clearer
If I try to add the following script:
{% set services = [ "scripts.mop_kitchen", "scripts.mop_hallway",
"scripts.mop_bedroom", "scripts.mop_living_room"] %}
{% set ns = namespace(x = services) %}
{% for i in range(ns.x | length - 1, 0, -1) %}
{% set j = range(0, i + 1) | random %}
{% if j != i %}
{% set ns.x = ns.x[:j]+[ns.x[i]]+ns.x[j+1:i]+[ns.x[j]]+ns.x[i+1:] %}
{% endif %}
{% endfor %}
alias: Mop Everywhere (Random Order)
sequence:
{% for room in ns.x %}
- service: {{ room }}
data: {}
- wait_template: {{ is_state('vacuum.xiaomi_vacuum_cleaner', 'returning') }}
{% endfor %}
mode: queued
max: 3
The home assistant UI returns Message malformed: Integration '' not found
random_room_mopping:
alias: Random room mopping
sequence:
- variables:
rooms: >
{% set services = [ "scripts.mop_kitchen", "scripts.mop_hallway",
"scripts.mop_bedroom", "scripts.mop_living_room"] %}
{% set ns = namespace(x = services) %}
{% for i in range(ns.x | length - 1, 0, -1) %}
{% set j = range(0, i + 1) | random %}
{% if j != i %}
{% set ns.x = ns.x[:j]+[ns.x[i]]+ns.x[j+1:i]+[ns.x[j]]+ns.x[i+1:] %}
{% endif %}
{% endfor %}
{{ ns.x }}
- repeat:
count: '{{ rooms | length }}'
sequence:
- service: '{{ rooms[repeat.index-1] }}'
- wait_template: "{{ is_state('vacuum.xiaomi_vacuum_cleaner', 'returning') }}"
I can’t test it because I don’t have a robot vacuum. However, I did test the following script, which simply posts a notification, and it worked correctly.
Click to show test script
mopping_test:
alias: Mopping Test
sequence:
- variables:
rooms: >
{% set services = [ "scripts.mop_kitchen", "scripts.mop_hallway",
"scripts.mop_bedroom", "scripts.mop_living_room"] %}
{% set ns = namespace(x = services) %}
{% for i in range(ns.x | length - 1, 0, -1) %}
{% set j = range(0, i + 1) | random %}
{% if j != i %}
{% set ns.x = ns.x[:j]+[ns.x[i]]+ns.x[j+1:i]+[ns.x[j]]+ns.x[i+1:] %}
{% endif %}
{% endfor %}
{{ ns.x }}
- repeat:
count: '{{ rooms | length }}'
sequence:
- service: persistent_notification.create
data:
title: '{{ repeat.index-1 }}'
message: '{{ rooms[repeat.index-1] }}'
- delay: "00:00:05"
Success! If I try to use the UI to create this script, it fails to validate (even when I remove the random_room_mopping key), but it does indeed work as expected when I insert it into the scripts.yml
Thank you very much for taking the time to help me with this!
Sorry, I can’t help you with that because I never use the Script Editor.
You’re welcome!
Please consider marking my post (above) with the Solution tag. It will automatically place a check-mark next to the topic’s title, signaling to other users that this topic has an accepted solution. It will also place a link below your first post that leads directly to the Solution post. All of this helps users find answers to similar questions.
Update: I moved to using Valetudo, and now I use the following script to mop all “segments” in random order, making sure to leave out any segments that I don’t want to mop (carpeted rooms).
The ignored segment in this case is number 18.
All the segment configuration comes from Valetudo directly.
I used the notification section to prove that the order is randomised - you will probably want to remove this if you plan on using it seriously.
My vacuum is called henry, make sure the MQTT topic is correct for your device.
mop_everywhere_random_order:
mode: queued
icon: mdi:broom
max: 10
alias: Mop Everywhere (Random Order)
sequence:
- variables:
segments: "{#- Use the Fisher-Yates shuffle algorithm -#} {#- https://www.geeksforgeeks.org/shuffle-a-given-array-using-fisher-yates-shuffle-algorithm/\
\ -#} {%- set ns = namespace( x = (expand('sensor.map_segments')[0].attributes\
\ | list), y = [], denylist = [18]) -%} {%- for\
\ i in range(ns.x | length - 1, 0, -1) %}\n {%- set j = range(0, i + 1) |\
\ random %}\n {%- if j != i %}\n {%- set ns.x = ns.x[:j]+[ns.x[i]]+ns.x[j+1:i]+[ns.x[j]]+ns.x[i+1:]\
\ %}\n {%- endif %}\n{%- endfor %} {#- Remove the non-numeric and denylisted\
\ elements from the list -#} {%- for i in range(ns.x | length - 1, 0, -1)\
\ %}\n {%- if ns.x[i] | int != 0 and ns.x[i] | int not in ns.denylist %}\n\
\ {%- set ns.y = ns.y + [ns.x[i] | int] %}\n {%- endif %}\n{%- endfor\
\ %} {{ ns.y }}\n"
- service: notify.mobile_app_iphone_12_pro
data:
message: Cleaning in random order {{ segments }}
title: Henry
- service: mqtt.publish
data:
topic: valetudo/henry/MapSegmentationCapability/clean/set
payload: '{"segment_ids": {{ segments }}, "customOrder": true }'
This is great, thanks for this (and to Taras too obviously).
One thing I noticed from my own use though: when your script iterates through the randomised list the second time to remove the non-integer elements, I believe the way you have it at the moment means it doesn’t include the last element in the randomised list - I therefore changed it to simply iterate upwards using the length of the list as the iteration range i.e. {%- for i in range(ns.x | length) %}.
The other thing is that HA complains about not setting a default value for int, so I also added that: if ns.x[i] | int(0) != 0 etc.
But really appreciate what you posted here, and it solved a real headscratcher for me.