Hey guys,
since it took me a while to get the vacuum automation to fit my personal needs, I thought I share my setup with you guys. Maybe somebody can get inspiration from it. I’m using xiaomi/roborock vacuums (Gen1 and two S50), all of them running custom firmware valetudo from rand256 (fork of original valetudo). This fork has an option to reload a saved map once the vacuum returns to the dock, which I personally need. Also, the node red automations that I’m using are utilising the API from that fork (not sure if the valetudo version from hypfer is the same). I’m not going to cover the installation of valetudo in this guide, but if you have questions, just let me know.
Requirements:
- clean rooms and zones individually, not just a complete clean
- chain room vacuuming. Start the cleaning in room a and while the robot is still active, add another room to be cleaned. Make it configurable, not just by creating static automations with fixed room/zones combinations.
- inform me when the dust bin is (most lilkely) full when I’m starting a new cleaning run, wait until it’s empty, then start the run
- start the cleaning from the individual room by scanning NFC (you could also use zigbee/zwave buttons or something else as a trigger of course)
- start the cleaning from lovelace as well, using the same logic.
- create the automation in node red.
high level description
So, here is how I achieved that in plain english.
home assistant
- create an input_select that holds the names of the rooms and zones that should be cleaned, plus one entry when the vacuum is docked
- place cheap nfc tags in the rooms where you want the vacuum to clean and add them in the home assistant app (should be straight forward, just note down the ncf tag ids for each room)
- create an input_number entry for the qm² the robot has cleaned
- create lovelace card to change input_select (use whatever card type you like)
node red
plugins needed:
- home assistant plugin. I think the addon also works, but I’m using the websocket plugin
- simple message queue
Flow overview
- listen to all events, filter out payload.event.tag_id messages that contain the specific nfc tag for your room and then set the input_select entity to that room name
- event_state node that listens to all state changes for said input_select entry and add it to the cleaning queue. The queue will hold all items send to it, until the queue is triggered, releasing the oldest item that was sent to the queue.
-
check input_number for qm² cleaned, whether it is greater than x qm² (I’m using 50 here).
If true: send the robot to the spot next to your garbage bin, wait until it is idle again, play the integrated sound file for “please empty the vacuum bin”, wait until bin is in the vacuum again, then trigger the cleaning queue to release the queue and reset the input_number form qm² cleaned to 0 since the vacuum bin was emptied.
If not true: trigger the cleaning queue to release the queue immediately. -
once the queued item is released, check the state of input_select that was send by the queue and start the appropriate cleaning for that room.
-
scanning of further rooms/zones (nfc tags) will repeat the process, but any additional change of the input_select will be queued until specifically released.
-
entity node which will trigger once the vacuum has a state of returning, meaning, it finished the cleaning of a room/zone and returns to the dock. This will send a trigger message to the queue to release the next item. If the queue is empty, nothing happens, if there is a “room” in the queue, the flow will go again through the switch and start the appropriate room/zone cleaning
-
entity node which will trigger once the vacuum has docked again
- set the input_select to state docked. This is just in case the first room cleaning after docking is the same as the last one before docking. If you’re not doing that, there is no state change to trigger the cleaning
- reset the queue, just in case
- use a function node to get the state from input_number, get the last cleaning_are attribute from the vacuum, add these two together and update the input_number accordingly.
So, in essence you need two helper, one input_select with all your rooms/zone as options, plus docked and one input_numer with steps as 0.01 and max value of 1000. Furthermore, I’m adding my node red flow here and some shiny pictures
Two things I should mention:
- state changes from the vacuum take a couple of seconds. If the vacuum has finished one room, it takes 2 -3 seconds until node red get’s notified of the state change to return and can start the next room
- the attribute change for “vacuum bin was put in again” takes roughly 20 - 30 seconds, so be patient.
[{"id":"cd7e28a7.95fde8","type":"server-events","z":"37a3306b.57211","name":"","server":"c52db6a3.01673","event_type":"","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"x":240,"y":320,"wires":[["4b8446ca.71705","dafa6280.66171"]]},{"id":"4b8446ca.71705","type":"switch","z":"37a3306b.57211","name":"Filter NFC tag living_room","property":"payload.event.tag_id","propertyType":"msg","rules":[{"t":"cont","v":"1234-abcd","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":550,"y":300,"wires":[["2e7dbd42.00dc02"]]},{"id":"dafa6280.66171","type":"switch","z":"37a3306b.57211","name":"Filter NFC tag kitchen","property":"payload.event.tag_id","propertyType":"msg","rules":[{"t":"cont","v":"4321-dcba","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":480,"y":360,"wires":[["7f61d2f9.ab1c54"]]},{"id":"2e7dbd42.00dc02","type":"api-call-service","z":"37a3306b.57211","name":"Vacuum ground floor set Zone living room","server":"c52db6a3.01673","version":1,"debugenabled":false,"service_domain":"input_select","service":"select_option","entityId":"input_select.vacuum_groundfloor","data":"{\"option\":\"livingroom\"}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":880,"y":300,"wires":[[]]},{"id":"7f61d2f9.ab1c54","type":"api-call-service","z":"37a3306b.57211","name":"Vacuum ground floor set Zone living room","server":"c52db6a3.01673","version":1,"debugenabled":false,"service_domain":"input_select","service":"select_option","entityId":"input_select.vacuum_groundfloor","data":"{\"option\":\"kitchen\"}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":880,"y":360,"wires":[[]]},{"id":"38f0f81.521b988","type":"server-state-changed","z":"37a3306b.57211","name":"vacuum ground floor zone set","server":"c52db6a3.01673","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_select.vacuum_groundfloor","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"docked","halt_if_type":"str","halt_if_compare":"is_not","outputs":2,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":160,"y":640,"wires":[["ea89c776.2de53","6f7e282e.065d9"],[]]},{"id":"6f7e282e.065d9","type":"simple-queue","z":"37a3306b.57211","name":"","firstMessageBypass":false,"bypassInterval":"0","x":790,"y":720,"wires":[["6378c458.c7c86c"]]},{"id":"ae9b1943.a8de5","type":"change","z":"37a3306b.57211","name":"trigger next run","rules":[{"t":"set","p":"trigger","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":720,"wires":[["6f7e282e.065d9"]]},{"id":"65735916.9d1cc","type":"change","z":"37a3306b.57211","name":"Reset queue","rules":[{"t":"set","p":"reset","pt":"msg","to":"1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":820,"wires":[["6f7e282e.065d9"]]},{"id":"d6f288ee.ea51f","type":"server-state-changed","z":"37a3306b.57211","name":"vacuum groundfloor has docked","server":"c52db6a3.01673","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"vacuum.groundfloor","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"docked","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":170,"y":820,"wires":[["65735916.9d1cc","9b36e0f7.11dce8","b283e3fb.a8d6c8"],[]]},{"id":"fc036ba3.7e60e","type":"server-state-changed","z":"37a3306b.57211","name":"vacuum ground floor returning > trigger","server":"c52db6a3.01673","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"vacuum.groundfloor","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"returning","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":190,"y":720,"wires":[["ae9b1943.a8de5"],[]]},{"id":"9b36e0f7.11dce8","type":"api-call-service","z":"37a3306b.57211","name":"vacuum groundfloor set docked","server":"c52db6a3.01673","version":1,"debugenabled":false,"service_domain":"input_select","service":"select_option","entityId":"input_select.staubsauger_groundfloor","data":"{\"option\":\"docked\"}","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":590,"y":880,"wires":[[]]},{"id":"73323de8.32e224","type":"comment","z":"37a3306b.57211","name":"Vacuum Zone Queue ground floor","info":"nfc tag sets zone to living room\nwhen zone is not docked, check against qm cleaned\nif > x qm, then play sound, wait until bin was put in again, trigger next run\nif < X trigger next run\nwhen returning, trigger next run (not needed, only one zone)\nwhen docked, reset queue, set zone to docked (so next nfc scan triggers a state change), add last clean area to Living room qm cleaned","x":240,"y":540,"wires":[]},{"id":"643c932a.34a9a4","type":"api-call-service","z":"37a3306b.57211","name":"vacuum groundfloor [play sound empty bin]","server":"c52db6a3.01673","version":1,"debugenabled":true,"service_domain":"vacuum","service":"send_command","entityId":"vacuum.groundfloor","data":"\t{\t \"command\":\"play_sound\",\t \"entity_id\": \"vacuum.groundfloor\",\t \"params\": {\t \"file\": \"/mnt/data/rockrobo/sounds/clean_bin.wav\"\t }\t \t}\t","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1270,"y":500,"wires":[[]]},{"id":"43f0c79c.dba668","type":"ha-wait-until","z":"37a3306b.57211","name":"wait until last_bin_out changed","server":"c52db6a3.01673","outputs":2,"entityId":"vacuum.groundfloor","entityIdFilterType":"exact","property":"attributes.last_bin_out","comparator":"is_not","value":"last_bin_out","valueType":"msg","timeout":"45","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":true,"blockInputOverrides":true,"x":1370,"y":620,"wires":[["1208470d.652581","ae9b1943.a8de5"],[]]},{"id":"53c99e3c.4d9c98","type":"change","z":"37a3306b.57211","name":"mv last_bin_out ","rules":[{"t":"move","p":"data.attributes.last_bin_out","pt":"msg","to":"last_bin_out","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1100,"y":620,"wires":[["43f0c79c.dba668"]]},{"id":"ea89c776.2de53","type":"api-current-state","z":"37a3306b.57211","name":"qm cleaned ground floor > 50","server":"c52db6a3.01673","version":1,"outputs":2,"halt_if":"50","halt_if_type":"num","halt_if_compare":"gt","override_topic":true,"entity_id":"input_number.staubsauger_erdgeschoss_qm_cleaned","state_type":"num","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":440,"y":580,"wires":[["b2e9f1a3.a02b98","79411390.4b3ebc"],["ae9b1943.a8de5"]]},{"id":"b2e9f1a3.a02b98","type":"api-current-state","z":"37a3306b.57211","name":"get state from vacuum groundfloor","server":"c52db6a3.01673","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"vacuum.groundfloor","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":820,"y":620,"wires":[["53c99e3c.4d9c98"]]},{"id":"b283e3fb.a8d6c8","type":"function","z":"37a3306b.57211","name":"add last clean area to groundfloor qm²","func":"const globalHomeAssistant = global.get('homeassistant');\nvar area_old_str = globalHomeAssistant.homeAssistant.states[\"input_number.staubsauger_groundfloor_qm_cleaned\"].state;\nvar area_cur_str = msg.data.new_state.attributes.currentCleanArea;\nvar area_cur = Number(area_cur_str);\nvar area_old = Number(area_old_str);\nvar area_new = area_old + area_cur;\nmsg.payload = {};\nmsg.payload = area_new;\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":610,"y":960,"wires":[["16915af9.dcac9d"]]},{"id":"16915af9.dcac9d","type":"api-call-service","z":"37a3306b.57211","name":"set value for groundfloor eg qm cleaned","server":"c52db6a3.01673","version":1,"debugenabled":true,"service_domain":"input_number","service":"set_value","entityId":"input_number.staubsauger_groundfloor_qm_cleaned","data":"{\"value\": payload }\t","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":980,"y":960,"wires":[[]]},{"id":"6378c458.c7c86c","type":"switch","z":"37a3306b.57211","name":"room switch","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"livingroom","vt":"str"},{"t":"eq","v":"kitchen","vt":"str"},{"t":"eq","v":"docked","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":1050,"y":760,"wires":[["c6f6550e.6f6708"],["84a1d4ca.d20f08"],[]]},{"id":"c6f6550e.6f6708","type":"api-call-service","z":"37a3306b.57211","name":"Staubsauger groundfloor [living room]","server":"c52db6a3.01673","version":1,"debugenabled":true,"service_domain":"vacuum","service":"send_command","entityId":"vacuum.groundfloor","data":"\t{\t \"command\":\"segmented_cleanup\",\t \"entity_id\": \"vacuum.groundfloor\",\t \"params\": {\t \"segment_ids\": [\t \"livingroom\"\t ] }\t \t}\t","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1350,"y":680,"wires":[[]]},{"id":"84a1d4ca.d20f08","type":"api-call-service","z":"37a3306b.57211","name":"Staubsauger groundfloor [kitchen]","server":"c52db6a3.01673","version":1,"debugenabled":true,"service_domain":"vacuum","service":"send_command","entityId":"vacuum.groundfloor","data":"\t{\t \"command\":\"segmented_cleanup\",\t \"entity_id\": \"vacuum.groundfloor\",\t \"params\": {\t \"segment_ids\": [\t \"kitchen\"\t ] }\t \t}\t","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1340,"y":740,"wires":[[]]},{"id":"79411390.4b3ebc","type":"api-call-service","z":"37a3306b.57211","name":"vacuum ground floor empty spot","server":"c52db6a3.01673","version":1,"debugenabled":true,"service_domain":"vacuum","service":"send_command","entityId":"vacuum.groundfloor","data":"{\t \"command\":\"go_to\",\t \"entity_id\": \"vacuum.groundfloor\",\t \"params\": {\t \"spot_id\": \"empty_spot\"\t }\t \t}\t","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":710,"y":540,"wires":[["82ca6d02.eea118"]]},{"id":"82ca6d02.eea118","type":"ha-wait-until","z":"37a3306b.57211","name":"wait until idle","server":"c52db6a3.01673","outputs":2,"entityId":"vacuum.groundfloor","entityIdFilterType":"exact","property":"state","comparator":"is","value":"idle","valueType":"str","timeout":"45","timeoutType":"num","timeoutUnits":"minutes","entityLocation":"","entityLocationType":"none","checkCurrentState":true,"blockInputOverrides":true,"x":990,"y":500,"wires":[["643c932a.34a9a4"],[]]},{"id":"c52db6a3.01673","type":"server","name":"Home Assistant","legacy":false,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]
If you have any questions, just let me know.