Hello,
Has anyone was able to integrate the new Dyson 360 Vis Nav vacuum with home assistant?
Any update on this? I am also interested in this.
Ongoing this integration for Dyson but it seems like not supporting the vis nav.
Integration link
Interested on this as well
Dyson Vis Nav Integration with Home Assistant
Introduction
Hi All,
I hope you’re having a great year so far! As a fan of Dyson’s products and a proud owner of the Vis Nav, I’ve been exploring ways to enhance its functionality. Unfortunately, the Vis Nav lacks local connectivity, unlike other models like the Eye or Heurist, which utilize MQTT. This limitation has led me to seek alternative solutions.
Here is an image of my Vis Nav dashboard within Home Assistant. Please read more if you are interested in recreating this yourself:
The Challenge
- No Local Connectivity: The Vis Nav does not support local connectivity, making it challenging to integrate with home automation systems.
- Limited Notifications: The notifications from the Dyson app are infrequent and do not provide a clear indication of the robot’s status, especially for unscheduled cleans.
Proposed Solution
Notification Integration
To work around these limitations, I discovered a method to receive notifications from the Dyson app into Home Assistant. This involves:
- Turning on all notifications in the Dyson app.
- Using the companion app feature to receive notifications in Home Assistant.
So part of the workable solution is to turn all notifications on the Dyson app On and then use a feature of the companion app which allows the dyson app notifications to be received into home assistant.
This is called XX-XXXX Last notification where XX-XXXX is the model \ make of your phone.
So far I’ve only been able to see things like Scheduled clean started, Robot faults and Clean completed and if you don’t start the vis nav off with a schedule the first thing you will know is that its completed a clean as there is no notification for clean started.
Now because the updates are so infrequent I decided to haved state after 3600 secoonds which says no recent updates.
I have chosen to separate out the notification text, the timestamp and the notification title. Here is an example of my code in configuration.yaml
# Dyson Robot Title
dyson_robot_notification_title:
friendly_name: "Dyson Robot Notification Title"
value_template: >
{% set title = state_attr('sensor.sm_s908b_last_notification', 'android.title') %}
{% set timestamp_ms = state_attr('sensor.sm_s908b_last_notification', 'post_time') %}
{% if timestamp_ms %}
{% set timestamp_s = timestamp_ms / 1000 %}
{% set current_time = now().timestamp() %}
{% set time_difference = current_time - timestamp_s %}
{% if time_difference <= 3600 %}
{{ title if title else 'No Title' }}
{% else %}
No recent updates
{% endif %}
{% else %}
No recent updates
{% endif %}
# Dyson Robot Text
dyson_robot_notification_text:
friendly_name: "Dyson Robot Notification Text"
value_template: >
{% set text = state_attr('sensor.sm_s908b_last_notification', 'android.text') %}
{% set timestamp_ms = state_attr('sensor.sm_s908b_last_notification', 'post_time') %}
{% if timestamp_ms %}
{% set timestamp_s = timestamp_ms / 1000 %}
{% set current_time = now().timestamp() %}
{% set time_difference = current_time - timestamp_s %}
{% if time_difference <= 3600 %}
{{ text if text else 'No Message' }}
{% else %}
No recent updates
{% endif %}
{% else %}
No recent updates
{% endif %}
# Dyson Robot Timestamp
dyson_robot_notification_timestamp:
friendly_name: "Dyson Robot Notification Timestamp"
value_template: >
{% set timestamp_ms = state_attr('sensor.sm_s908b_last_notification', 'post_time') %}
{% if timestamp_ms %}
{% set timestamp_s = timestamp_ms / 1000 %}
{% set current_time = now().timestamp() %}
{% set time_difference = current_time - timestamp_s %}
{% if time_difference <= 3600 %}
{{ timestamp_s | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}
{% else %}
No recent updates
{% endif %}
{% else %}
No recent updates
{% endif %}
I have an automation looking at the Notification text so that whenever it changes it will be checked and if it’s a message starting with “Your robot, Dyson 360 Upstairs” or “Your robot, Dyson 360 Downstairs” then a text helper for either the upstairs or downstairs robot is updated with the notification title.
Here is a copy of my automation:
alias: Dyson Monitoring Automation
description: An automation to monitor the dyson cleaners
triggers:
- entity_id: sensor.dyson_robot_notification_text
trigger: state
actions:
- choose:
- conditions:
- condition: template
value_template: >
{{ states('sensor.dyson_robot_notification_text').startswith('Your
robot, Dyson 360 Downstairs') }}
sequence:
- target:
entity_id: input_text.dyson_downstairs_status
data:
value: "{{ states('sensor.dyson_robot_notification_title') }}"
action: input_text.set_value
- conditions:
- condition: template
value_template: >
{{ states('sensor.dyson_robot_notification_text').startswith('Your
robot, Dyson 360 Upstairs') }}
sequence:
- target:
entity_id: input_text.dyson_upstairs_status
data:
value: "{{ states('sensor.dyson_robot_notification_title') }}"
action: input_text.set_value
mode: single
Its very basic but as long as I schedule all my cleans I can work out whether my robots are running from the notification message as mentioned earlier unscheduled cleans don’t produce a start notification.
This is what it all looks like in home assistant, note that my upstairs robot has not sent a notification yet so ignore the blank box.
Next part of this is to create a helper button an an automation which checks the state of the button to sends a message to alexa media player to turn on the robot, or turn it off. To do this you’ll need to have linked the dyson app to alexa and given your robot a suitable name.
Here is my example code to do this:
alias: Dyson Robot Toggle on and off
description: ""
triggers:
- trigger: state
entity_id:
- input_boolean.dyson_robot
from: "off"
to: "on"
id: Robot On
- trigger: state
entity_id:
- input_boolean.dyson_robot
from: "on"
to: "off"
id: Robot Off
conditions: []
actions:
- data: {}
target:
entity_id: switch.armed
action: switch.turn_off
- if:
- condition: trigger
id:
- Robot On
then:
- data:
media_content_id: turn on dyson 360 upstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
else:
- data:
media_content_id: turn off dyson 360 upstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
mode: single
Now to take this one stage further if you absolutely know you’re only going to trigger the dyson robot from home assistant you could build into this automation a simple set value to set the status to “Clean initiated” or something similar.
Now of course you don’t have to use the voice linking which dyson provides so you could call your dyson robot “biscuit” in the dyson app and in alexa app.
Then using the https://alexa-node-red.bm.hardill.me.uk/ nodered alexa skill bridge you could effectively say to alexa “turn on dyson” where dyson is the name of the is the name of the device as defined in the node-red alexa skill bridge then using nodered you could take that voice command interpret it as a switch turn on and using that switch have an automation that calls alexa media player with “Turn on biscuit” or “Turn off biscuit” respectively.
Here are my recently written automations along with content from configuration.yaml
Configuration.yaml
# Dyson cleaning timers
input_text:
dyson_upstairs_stopwatch:
name: Dyson Upstairs Cleaning Time
initial: "00:00:00"
dyson_downstairs_stopwatch:
name: Dyson Downstairs Cleaning Time
initial: "00:00:00"
dyson_upstairs_last_clean:
name: Dyson Upstairs Last Clean Duration
initial: "Not cleaned yet"
dyson_downstairs_last_clean:
name: Dyson Downstairs Last Clean Duration
initial: "Not cleaned yet"
input_boolean:
stop_upstairs_timer:
name: Stop Upstairs Timer
stop_downstairs_timer:
name: Stop Downstairs Timer
recorder:
exclude:
entities:
- input_text.dyson_upstairs_stopwatch
- input_text.dyson_downstairs_stopwatch
Automations:
alias: Dyson Clean Complete update Booleans and record cleaning times
description: Updates the booleans when the cleaning is complete
triggers:
- trigger: state
entity_id:
- input_text.dyson_upstairs_status
to: Clean complete
id: Clean Complete US
- trigger: state
entity_id:
- input_text.dyson_downstairs_status
to: Clean complete
id: Clean Complete DS
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id:
- Clean Complete US
sequence:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.dyson_robot_upstairs
- target:
entity_id: input_text.dyson_upstairs_last_clean
data:
value: "{{ states('input_text.dyson_upstairs_stopwatch') }}"
action: input_text.set_value
- action: script.send_message_to_platform
data:
messagecontent: >-
Dyson Upstairs clean completed, time taken: {{
states('input_text.dyson_upstairs_stopwatch') }}
- target:
entity_id: input_boolean.stop_upstairs_timer
action: input_boolean.turn_on
data: {}
- delay:
seconds: 1
- target:
entity_id: input_boolean.stop_upstairs_timer
action: input_boolean.turn_off
data: {}
- conditions:
- condition: trigger
id:
- Clean Complete DS
sequence:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.dyson_robot_downstairs
- target:
entity_id: input_text.dyson_downstairs_last_clean
data:
value: "{{ states('input_text.dyson_downstairs_stopwatch') }}"
action: input_text.set_value
- action: script.send_message_to_platform
data:
messagecontent: >-
Dyson Downstairs clean completed, time taken: {{
states('input_text.dyson_downstairs_stopwatch') }}
- target:
entity_id: input_boolean.stop_downstairs_timer
action: input_boolean.turn_on
data: {}
- delay:
seconds: 1
- target:
entity_id: input_boolean.stop_downstairs_timer
action: input_boolean.turn_off
data: {}
mode: single
alias: Dyson Extended Cleaning Time Alert
description: Alerts when Dyson cleaning exceeds 35 minutes
triggers:
- value_template: >-
{% set time = states('input_text.dyson_upstairs_stopwatch') %} {% set
minutes = (time.split(':')[0] | int * 60) + (time.split(':')[1] | int) %}
{{ minutes >= 35 }}
trigger: template
- value_template: >-
{% set time = states('input_text.dyson_downstairs_stopwatch') %} {% set
minutes = (time.split(':')[0] | int * 60) + (time.split(':')[1] | int) %}
{{ minutes >= 35 }}
trigger: template
actions:
- choose:
- conditions:
- condition: template
value_template: >-
{% set time = states('input_text.dyson_upstairs_stopwatch') %} {%
set minutes = (time.split(':')[0] | int * 60) +
(time.split(':')[1] | int) %} {{ minutes >= 35 }}
sequence:
- data:
messagecontent: >-
Dyson Upstairs cleaning time has exceeded 35 minutes: {{
states('input_text.dyson_upstairs_stopwatch') }}, resetting
status
action: script.send_message_to_platform
- target:
entity_id: input_text.dyson_upstairs_status
data:
value: Ready to clean
action: input_text.set_value
- target:
entity_id: input_boolean.dyson_robot_upstairs
action: input_boolean.turn_off
data: {}
- conditions:
- condition: template
value_template: >-
{% set time = states('input_text.dyson_downstairs_stopwatch') %}
{% set minutes = (time.split(':')[0] | int * 60) +
(time.split(':')[1] | int) %} {{ minutes >= 35 }}
sequence:
- data:
messagecontent: >-
Dyson Downstairs cleaning time has exceeded 35 minutes: {{
states('input_text.dyson_downstairs_stopwatch') }}, resetting
status
action: script.send_message_to_platform
- target:
entity_id: input_text.dyson_downstairs_status
data:
value: Ready to clean
action: input_text.set_value
- target:
entity_id: input_boolean.dyson_robot_downstairs
action: input_boolean.turn_off
data: {}
mode: single
alias: Dyson Monitoring Automation
description: An automation to monitor the dyson cleaners
triggers:
- entity_id: sensor.dyson_robot_notification_text
trigger: state
- entity_id: sensor.dyson_robot_notification_timestamp
trigger: state
actions:
- choose:
- conditions:
- condition: template
value_template: >
{{ states('sensor.dyson_robot_notification_text').startswith('Your
robot, Dyson 360 Downstairs') }}
sequence:
- target:
entity_id: input_text.dyson_downstairs_status
data:
value: "{{ states('sensor.dyson_robot_notification_title') }}"
action: input_text.set_value
- conditions:
- condition: template
value_template: >
{{ states('sensor.dyson_robot_notification_text').startswith('Your
robot, Dyson 360 Upstairs') }}
sequence:
- target:
entity_id: input_text.dyson_upstairs_status
data:
value: "{{ states('sensor.dyson_robot_notification_title') }}"
action: input_text.set_value
mode: single
alias: Dyson Robot Toggle on and off and cancel
description: ""
triggers:
- trigger: state
entity_id:
- input_boolean.dyson_robot_upstairs
id: Robot US
- trigger: state
entity_id:
- input_boolean.dyson_robot_downstairs
id: Robot DS
- trigger: state
entity_id:
- input_button.dyson_cancel_all_cleaning_operations
id: Cancel All
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id:
- Robot US
- condition: state
entity_id: input_boolean.dyson_robot_upstairs
state: "on"
sequence:
- action: input_text.set_value
metadata: {}
data:
value: Clean started
target:
entity_id: input_text.dyson_upstairs_status
- data:
media_content_id: turn on dyson 360 upstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- event: start_upstairs_stopwatch
- delay:
seconds: 10
- action: input_text.set_value
metadata: {}
data:
value: Clean in progress
target:
entity_id: input_text.dyson_upstairs_status
- conditions:
- condition: trigger
id:
- Robot US
- condition: state
entity_id: input_boolean.dyson_robot_upstairs
state: "off"
sequence:
- action: input_text.set_value
metadata: {}
data:
value: Clean complete
target:
entity_id: input_text.dyson_upstairs_status
- data:
media_content_id: turn off dyson 360 upstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- conditions:
- condition: trigger
id:
- Robot DS
- condition: state
entity_id: input_boolean.dyson_robot_downstairs
state: "on"
sequence:
- action: input_text.set_value
metadata: {}
data:
value: Clean started
target:
entity_id: input_text.dyson_downstairs_status
- data:
media_content_id: turn on dyson 360 downstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- event: start_downstairs_stopwatch
- delay:
seconds: 10
- action: input_text.set_value
metadata: {}
data:
value: Clean in progress
target:
entity_id: input_text.dyson_downstairs_status
- conditions:
- condition: trigger
id:
- Robot DS
- condition: state
entity_id: input_boolean.dyson_robot_downstairs
state: "off"
sequence:
- action: input_text.set_value
metadata: {}
data:
value: Clean complete
target:
entity_id: input_text.dyson_downstairs_status
- data:
media_content_id: turn off dyson 360 downstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- data:
media_content_id: turn off dyson 360 downstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- conditions:
- condition: trigger
id:
- Cancel All
sequence:
- data:
media_content_id: turn off dyson 360 upstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- data:
media_content_id: turn off dyson 360 downstairs
media_content_type: custom
target:
entity_id:
- media_player.entry
action: media_player.play_media
- delay:
hours: 0
minutes: 1
seconds: 30
milliseconds: 0
- action: input_text.set_value
metadata: {}
data:
value: Ready to clean
target:
entity_id:
- input_text.dyson_upstairs_status
- input_text.dyson_downstairs_status
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id:
- input_boolean.dyson_robot_upstairs
- input_boolean.dyson_robot_downstairs
mode: single
alias: Dyson Status Reset After Complete
description: Changes status to Ready to clean after completion
triggers:
- entity_id: input_text.dyson_upstairs_status
to: Clean complete
id: upstairs
trigger: state
- entity_id: input_text.dyson_downstairs_status
to: Clean complete
id: downstairs
trigger: state
actions:
- choose:
- conditions:
- condition: trigger
id: upstairs
sequence:
- delay:
hours: 0
minutes: 1
seconds: 30
- target:
entity_id: input_text.dyson_upstairs_status
data:
value: Ready to clean
action: input_text.set_value
- conditions:
- condition: trigger
id: downstairs
sequence:
- delay:
hours: 0
minutes: 1
seconds: 30
- target:
entity_id: input_text.dyson_downstairs_status
data:
value: Ready to clean
action: input_text.set_value
mode: parallel
alias: Dyson Turn on downstairs lights while cleaning is in progress
description: ""
triggers:
- trigger: state
entity_id:
- input_text.dyson_downstairs_status
to: Clean in progress
id: Cleaning in progress
- trigger: state
entity_id:
- input_text.dyson_downstairs_status
to: Ready to clean
id: Cleaning complete
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id:
- Cleaning in progress
sequence:
- action: light.turn_on
metadata: {}
data: {}
target:
entity_id:
- light.kitchen_1
- light.kitchen_2
- light.kitchen_3
- light.kitchen_4
- light.downstairs_hall
- light.entrance_bulb
- action: switch.turn_on
metadata: {}
data: {}
target:
entity_id:
- switch.sonoff_1001613ff9
- switch.sonoff_10016143b3
- conditions:
- condition: trigger
id:
- Cleaning complete
sequence:
- action: light.turn_off
metadata: {}
data: {}
target:
entity_id:
- light.kitchen_1
- light.kitchen_2
- light.kitchen_3
- light.kitchen_4
- light.downstairs_hall
- light.entrance_bulb
- action: switch.turn_off
metadata: {}
data: {}
target:
entity_id:
- switch.sonoff_1001613ff9
- switch.sonoff_10016143b3
mode: single
alias: Send Platform message for Dyson Faults
description: Notifies Platform when Dyson robot faults are detected
triggers:
- entity_id: input_text.dyson_upstairs_status
trigger: state
to: Fault detected
- entity_id: input_text.dyson_downstairs_status
trigger: state
to: Fault detected
conditions: []
actions:
- choose:
- conditions:
- condition: template
value_template: "{{ trigger.entity_id == 'input_text.dyson_upstairs_status' }}"
sequence:
- action: script.send_message_to_platform
data:
messagecontent: >-
Dyson Robot Upstairs: {{
states('sensor.dyson_robot_notification_text') }} {%- if
states('sensor.dyson_robot_notification_text') == 'Your robot,
Dyson 360 Upstairs, needs your assistance.' %} Routine Check{%
endif %}
- target:
entity_id: input_boolean.stop_upstairs_timer
action: input_boolean.turn_on
data: {}
- delay:
seconds: 1
- target:
entity_id: input_boolean.stop_upstairs_timer
action: input_boolean.turn_off
data: {}
- conditions:
- condition: template
value_template: "{{ trigger.entity_id == 'input_text.dyson_downstairs_status' }}"
sequence:
- action: script.send_message_to_platform
data:
messagecontent: >-
Dyson Robot Downstairs: {{
states('sensor.dyson_robot_notification_text') }} {%- if
states('sensor.dyson_robot_notification_text') == 'Your robot,
Dyson 360 Downstairs, needs your assistance.' %} Routine Check{%
endif %}
- target:
entity_id: input_boolean.stop_downstairs_timer
action: input_boolean.turn_on
data: {}
- delay:
seconds: 1
- target:
entity_id: input_boolean.stop_downstairs_timer
action: input_boolean.turn_off
data: {}
mode: single
alias: Start Dyson Downstairs Stopwatch
triggers:
- event_type: start_downstairs_stopwatch
trigger: event
actions:
- target:
entity_id: input_text.dyson_downstairs_stopwatch
data:
value: "00:00:00"
action: input_text.set_value
- repeat:
sequence:
- delay:
seconds: 1
- target:
entity_id: input_text.dyson_downstairs_stopwatch
data:
value: >
{% set time = states('input_text.dyson_downstairs_stopwatch') %}
{% set hours = time.split(':')[0] | int %} {% set minutes =
time.split(':')[1] | int %} {% set seconds = time.split(':')[2] |
int %} {% set total_seconds = hours * 3600 + minutes * 60 +
seconds + 1 %} {% set new_hours = (total_seconds // 3600) %} {%
set new_minutes = ((total_seconds % 3600) // 60) %} {% set
new_seconds = total_seconds % 60 %} {{ '%02d:%02d:%02d' |
format(new_hours, new_minutes, new_seconds) }}
action: input_text.set_value
until:
- condition: state
entity_id: input_boolean.stop_downstairs_timer
state: "on"
alias: Start Dyson Upstairs Stopwatch
triggers:
- event_type: start_upstairs_stopwatch
trigger: event
actions:
- target:
entity_id: input_text.dyson_upstairs_stopwatch
data:
value: "00:00:00"
action: input_text.set_value
- repeat:
sequence:
- delay:
seconds: 1
- target:
entity_id: input_text.dyson_upstairs_stopwatch
data:
value: >
{% set time = states('input_text.dyson_upstairs_stopwatch') %} {%
set hours = time.split(':')[0] | int %} {% set minutes =
time.split(':')[1] | int %} {% set seconds = time.split(':')[2] |
int %} {% set total_seconds = hours * 3600 + minutes * 60 +
seconds + 1 %} {% set new_hours = (total_seconds // 3600) %} {%
set new_minutes = ((total_seconds % 3600) // 60) %} {% set
new_seconds = total_seconds % 60 %} {{ '%02d:%02d:%02d' |
format(new_hours, new_minutes, new_seconds) }}
action: input_text.set_value
until:
- condition: state
entity_id: input_boolean.stop_upstairs_timer
state: "on"
alias: Dyson Turn on upstairs lights while cleaning is in progress
description: ""
triggers:
- trigger: state
entity_id:
- input_text.dyson_upstairs_status
to: Clean in progress
id: Cleaning in progress
- trigger: state
entity_id:
- input_text.dyson_upstairs_status
to: Ready to clean
id: Cleaning complete
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id:
- Cleaning in progress
sequence:
- action: light.turn_on
metadata: {}
data: {}
target:
entity_id:
- light.office_bulb
- light.spare_room_bulb
- light.bedroom_bulb_1
- light.bedroom_bulb_2
- action: switch.turn_on
metadata: {}
data: {}
target:
entity_id:
- switch.sonoff_10015b64c0
- conditions:
- condition: trigger
id:
- Cleaning complete
sequence:
- action: light.turn_off
metadata: {}
data: {}
target:
entity_id:
- light.office_bulb
- light.spare_room_bulb
- light.bedroom_bulb_1
- light.bedroom_bulb_2
- action: switch.turn_off
metadata: {}
data: {}
target:
entity_id:
- switch.sonoff_10015b64c0
mode: single