So I added a second optional field and made the rendered map optional, bit of a hacky solution as you have to leave one empty, but let me know if you want me make a merge request for this (or if you know how to validate only 1 input being used)
blueprint:
name: 'Valetudo: Notifications'
description: Sends a notification after a successful clean-up run and on error.
source_url: https://github.com/mundschenk-at/ha-valetudo-blueprints/blob/13f3d07f981ed24736491a31fa4c605fcfdcba64/yaml/automation/valetudo-notifications.yaml
domain: automation
homeassistant:
min_version: 2022.10.0
input:
vacuum:
name: Vacuum Robot
description: The vacuum robot entity.
selector:
entity:
filter:
- domain: vacuum
- integration: mqtt
multiple: false
rendered_map:
name: Rendered Map (IOS)
description: Camera sensor of the map (Leave empty if using Android)
selector:
entity:
filter:
- domain: camera
multiple: false
default:
map_snapshot:
name: Map Snapshot (Android)
description: Location inside the www folder where a PNG snapshot is stored (Leave empty if using Rendered Map above)
default: '/local/snapshot_yourvacuum.png'
error_message:
name: Error Message
description: The sensor containing the last error message.
selector:
entity:
filter:
- domain: sensor
- integration: mqtt
multiple: false
dashboard_url:
name: Dashboard URL
description: The dashboard that should be opened when tapping on the notification.
default: /lovelace/
selector:
text:
type: url
multiline: false
notify_device:
name: Notification Device(s)
description: The notified device(s).
default: false
selector:
device:
filter:
- integration: mobile_app
multiple: true
notify_group:
name: Notification Group
description: The name of the notification group to call. ('notify' for all devices)
default: ''
language:
name: Language
description: The language used for the notifications.
default: English
selector:
select:
options:
- English
- German
sort: false
custom_value: false
multiple: false
trigger_variables:
rendered_map_sensor: !input rendered_map
map_snapshot_sensor: !input map_snapshot
error_message_sensor: !input error_message
notify_devices: !input notify_device
group_target: !input notify_group
language: !input language
trigger:
- platform: state
entity_id: !input vacuum
from: cleaning
to: returning
id: cleaning_finished
variables:
msg_type: success_msg
- platform: state
entity_id: !input vacuum
to: error
for:
hours: 0
minutes: 0
seconds: 5
id: error
variables:
msg_type: error_msg
condition:
- condition: or
alias: Finished cleaning task or error message is not empty
conditions:
- condition: trigger
id: cleaning_finished
- condition: not
conditions:
- condition: state
entity_id: !input error_message
state: ''
variables:
strings:
English:
title: Vacuum
success_msg: Cleaning task succeeded at {{ now().strftime('%-H:%M') }}.
error_msg: 'The robot has encountered an issue: {{ states( error_message_sensor
) }}'
default_msg: Everything is working fine.
German:
title: Staubsauger
success_msg: Die Reinigung wurde um {{ now().strftime('%-H:%M') }} Uhr erfolgreich
abgeschlossen.
error_msg: 'Der Staubsauger hat ein Problem: {{ states( error_message_sensor
) }}'
default_msg: Alles gut.
notify_targets: "{% set ns = namespace(targets=[]) %} {% if notify_devices is iterable
%}\n {% for device_id in notify_devices -%}\n {% set ns.targets = ns.targets
+ [ \"notify.mobile_app_\" ~ device_attr( device_id, 'name' )|slugify ] %}\n {%-
endfor %}\n{%- endif %} {% if group_target is defined and group_target is string
and group_target != '' %}\n {% set ns.targets = ns.targets + [ \"notify.\"
~ group_target|slugify ] %}\n{%- endif %} {{ns.targets}}"
title: '{{ strings[language][''title''] }}'
message: '{{ strings[language][msg_type] | default(strings[language][''default_msg''])
}}'
data:
entity_id: '{{ rendered_map_sensor | default(none) }}'
image: '{{ map_snapshot_sensor | default(none) }}'
priority: high
url: !input dashboard_url
clickAction: !input dashboard_url
action:
- repeat:
for_each: '{{ notify_targets }}'
sequence:
- service: '{{ repeat.item }}'
data:
title: '{{ title }}'
message: '{{ message }}'
data: '{{ data }}'
mode: queued
Thx, I’ll have to look into that. Maybe you can test a new version in the next few days. It should be possible to use camera_proxy instead of manually managing snapshots.
While I didn’t get around to the camera_proxy solution yet, I’ve released a new version of the blueprint that uses the Status Flag sensor provided by newer Valetudo releases to prevent multiple notifications from being sent when a cleaning run is interrupted, but resumed later.
Hey, I just found this and it’s super helpful! Thank you!
Before installing this, I started setting up binary sensors to collect which rooms I wanted to be vacuumed, as per the Valetudo documentation:
Each of my individual “Vacuum this room?” binary sensors is customized in /config/customize.yaml to have a room_id: attribute, with the segment name stored as the corresponding value.
These sensors are grouped under group.vacuum_rooms.
Additionally, I have a Number helper, input_number.vacuum_iterations, which I intend to use to set the number of vacuum passes.
I’ve created a “Valetudo: Clean Rooms” script using this blueprint. When I call that script, how can I pass the list of rooms to clean and the number of iterations to it from these entities?
The script takes a comma-separated list of room names, not the IDs. I dislike the many helper entities recommended in the official documentation, as they are not needed to convert room names to IDs (the blueprint does it with a bit of template magic). Also the number iterations is a script parameter as well.
Wanted to let you know the appreciation I have for you and the ‘helper’.
I used a Roborock S5 with Valetudo using this script, then replaced it with a Q Revo and missed the ease of executing automations. I returned the Q Revo and bought a refurb Dreame L10S and put Valetudo on it. Equal reasons because of the offline aspect of Valetudo and your helpers.
I have now added an image to the notification payload using the camera_proxy API. Can you (or someone else with an Android device) please check if this works correctly?
Just tried it on my side, but for now I can’t get it to display the actual room image, only the valetudo embedded image message.
EDIT : Ok now that I’ve read previous messages, I installed valetudopng, and it seem to work, but the image is cropped, i’ll have to figure out a way to have the full image.
I tried to add custom limits to valetudopng but something must be wrong, the container does start but image is not available if I don’t leave this blank.
Also I tried to simulate an error by lifting the robot while it was running, but I dont get notified, is there something I’m missing, or is it just not designed to do this ?
Unfortunately, that’s kind of hard to debug as I don’t have an Android device available. I’ve never had to modify the valetudopng output, but maybe that’s because apartment is more squarish?
You’d have to check with the author of valetudopng, but as I understand it, cropping/limits is only intended for when you don’t want to show the whole map. I don’t think this should be solved at the Dock container level.
That depends entirely on the robot in question. Sometimes my Dreame takes a bit of time until it detects that it is treading air. When the robot signals an error state, the notifications will trigger. If there is no such signal or Valetudo does not expose it, the notification won’t happen.
button_1:
name: Action Button 1 Text
description: 'The text used on the first Action button at the bottom of the notification. Set the URL below. (Default is "View Clip")'
default: "View Clip"
url_1:
name: Action Button 1 URL
description: Customise what happens when you press the first Action Button. Select from one of the preconfigured options or enter your own custom URL.
default: "{{base_url}}/api/frigate{{client_id}}/notifications/{{id}}/{{camera}}/clip.mp4"
selector:
select:
options:
- label: View Clip
value: "{{base_url}}/api/frigate{{client_id}}/notifications/{{id}}/{{camera}}/clip.mp4"
- label: View Snapshot
value: "{{base_url}}/api/frigate{{client_id}}/notifications/{{id}}/snapshot.jpg"
- label: View Stream
value: "{{base_url}}/api/camera_proxy_stream/camera.{{trigger.payload_json['after']['camera'] | lower | replace('-','_')}}?token={{state_attr( 'camera.' ~ camera, 'access_token')}}"
- label: Open Home Assistant (web)
value: "{{base_url}}/lovelace"
- label: Open Home Assistant (app)
value: /lovelace
- label: Open Frigate
value: /ccab4aaf_frigate/dashboard
- label: Open Frigate (Full Access)
value: /ccab4aaf_frigate-fa/dashboard
- label: Open Frigate (proxy)
value: /ccab4aaf_frigate-proxy/dashboard
- label: Open Reolink App (Android)
value: app://com.mcu.reolink
- label: Custom Action (Manual Trigger)
value: custom-{{ camera }}
custom_value: true
icon_1:
name: Action Button 1 icon - iOS Only
description: Customise the Icon on the first Action Button. Only the iOS SFSymbols library is supported, not mdi:icons. e.g., sfsymbols:bell
default: ""
The interactions redirects you to a clip, or a snapshot in your browser, here’s the result :
It’s an old Roborock S50, the “error” does change to something like “wheels not in contact in the floor” (I don’t remember the exact message) when lifting it.
If I understand the blueprint correctly, it’s the vacuum entity that triggers the notification, and it seems to change to error correctly
Made a few more tries, and it works, after reading your blueprint again, I noticed it has to stay in “error” state for 5 second before the automation triggers.
Once this was solved, I encountered an other issue, the notification kept displaying “Unknown error undefined”.
After looking at my HA logs, I noticed the error entity goes to this state for 7 to 9 seconds before displaying the “real” error.
So I modified your blueprint to trigger after 10 seconds instead of 5, and it worked like a charm, and now it displays “dustbin missing”, or “wheels not in contact with the floor” correctly
Yeah, maybe have a “classic” and a “Roborock” option, or just a 5 second default, but that you can edit in your automation to fit any robot’s needs, with a short explanation of why this is there.