Files
haos-blueprints/Common/Dreame Vacuum/blueprint.yaml
T
alexei.dolgolyov e3b9b123f2 fix: derive Dreame Vacuum name from device to avoid doubled friendly_name
The Dreame integration names the vacuum entity ' <device name>' (leading space),
so HA composes the entity friendly_name as the device name twice (e.g.
'Z10 Pro  Z10 Pro'). Derive vacuum_name from the device's name_by_user/name
instead, falling back to friendly_name then entity_id. Bump blueprint_version
to 1.2.1 and manifest to 2.14.2.
2026-06-22 15:54:57 +03:00

573 lines
24 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dreame Vacuum Notifications
# Sends notifications for Dreame vacuum events (cleaning, errors, consumables).
# See README.md for detailed documentation.
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
blueprint:
name: "Custom: Dreame Vacuum Notifications"
description: >
Sends customizable notifications for Dreame vacuum events including
cleaning status, consumable alerts, warnings, and errors.
Requires the Dreame Vacuum integration (github.com/Tasshack/dreame-vacuum).
domain: automation
# ===========================================================================
# INPUT CONFIGURATION
# ===========================================================================
input:
# -------------------------------------------------------------------------
# Vacuum Configuration
# -------------------------------------------------------------------------
vacuum_group:
name: "Vacuum"
collapsed: false
input:
vacuum_entity:
name: Vacuum Entity
description: "The Dreame vacuum entity to monitor."
selector:
entity:
integration: dreame_vacuum
domain: vacuum
# -------------------------------------------------------------------------
# Notification Configuration
# -------------------------------------------------------------------------
notification_group:
name: "Notification"
collapsed: false
input:
notify_targets:
name: Notification Targets
description: "Notification service entities to send messages to (select one or more)."
selector:
entity:
domain: notify
multiple: true
# -------------------------------------------------------------------------
# Event Toggles
# -------------------------------------------------------------------------
toggles_group:
name: "Event Toggles"
collapsed: false
input:
enable_cleaning_started:
name: Cleaning Started
description: "Send notification when the vacuum starts cleaning."
default: true
selector:
boolean:
enable_cleaning_completed:
name: Cleaning Completed
description: "Send notification when the vacuum finishes cleaning."
default: true
selector:
boolean:
enable_consumable:
name: Consumable Depleted
description: "Send notification when a consumable reaches end-of-life."
default: true
selector:
boolean:
enable_warning:
name: Warning
description: "Send notification for device warnings."
default: true
selector:
boolean:
enable_error:
name: Error
description: "Send notification for device errors."
default: true
selector:
boolean:
enable_information:
name: Information
description: "Send notification for informational alerts (e.g., blocked by DND)."
default: true
selector:
boolean:
# -------------------------------------------------------------------------
# Filtering
# -------------------------------------------------------------------------
filters_group:
name: "Filtering"
collapsed: true
input:
warning_codes_ignore:
name: Warning Codes to Ignore
description: >
List of warning codes to silence (one entry per code, as text).
Useful for suppressing routine warnings while keeping critical ones.
default: []
selector:
text:
multiple: true
error_codes_ignore:
name: Error Codes to Ignore
description: >
List of error codes to silence (one entry per code, as text).
default: []
selector:
text:
multiple: true
information_codes_ignore:
name: Information Types to Ignore
description: >
List of information message types to silence (one entry per type).
Information events carry a type id, not a numeric code.
Valid values: dust_collection, cleaning_paused.
default: []
selector:
text:
multiple: true
# -------------------------------------------------------------------------
# Message Templates
# -------------------------------------------------------------------------
messages_group:
name: "Message Templates"
collapsed: true
input:
message_cleaning_started:
name: "Cleaning Started Message"
description: >
Message sent when the vacuum starts cleaning.
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`
default: "🧹 {vacuum_name} started cleaning ({cleaning_mode})."
selector:
text:
multiline: true
message_cleaning_completed:
name: "Cleaning Completed Message"
description: >
Message sent when the vacuum finishes cleaning.
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`,
`{cleaned_area}`, `{cleaning_time}`
default: "✅ {vacuum_name} finished cleaning. Area: {cleaned_area} m², time: {cleaning_time} min."
selector:
text:
multiline: true
message_consumable:
name: "Consumable Depleted Message"
description: >
Message sent when a consumable reaches end-of-life.
Variables: `{vacuum_name}`, `{consumable}`
default: "🔧 {vacuum_name}: {consumable} needs replacement."
selector:
text:
multiline: true
message_warning:
name: "Warning Message"
description: >
Message sent for device warnings.
Variables: `{vacuum_name}`, `{warning}`, `{code}`, `{code_name}`
default: "⚠️ {vacuum_name} warning: {warning} (code: {code})."
selector:
text:
multiline: true
message_error:
name: "Error Message"
description: >
Message sent for device errors.
Variables: `{vacuum_name}`, `{error}`, `{code}`, `{code_name}`
default: "❌ {vacuum_name} error: {error} (code: {code})."
selector:
text:
multiline: true
message_information:
name: "Information Message"
description: >
Message sent for informational alerts.
Variables: `{vacuum_name}`, `{information}`
default: "️ {vacuum_name}: {information}."
selector:
text:
multiline: true
# -------------------------------------------------------------------------
# Localization / Label Overrides
# -------------------------------------------------------------------------
localization_group:
name: "Localization"
collapsed: true
input:
label_overrides:
name: "Label Overrides"
description: >
Optional translation/relabel table for dynamic values, as YAML
key/value pairs. Keys are the raw values the integration emits
(cleaning mode / status are UPPER_SNAKE enum names; consumable /
information are lower-case ids); values are your preferred text in
any language. Applied to {cleaning_mode}, {status}, {consumable}
and {information}. Example:
SWEEPING_AND_MOPPING: Подметание и мытьё
CLEANING: Уборка
BACK_HOME: Возврат на базу
dust_collection: Очистка контейнера
default: {}
selector:
object:
code_label_overrides:
name: "Warning/Error Code Labels"
description: >
Optional table mapping a numeric warning/error code to a short
label, exposed as the {code_name} placeholder. Leave empty to omit
{code_name}. Use unquoted numbers as keys. Example:
68: Снять швабру
47: Робот застрял
default: {}
selector:
object:
# -------------------------------------------------------------------------
# Debug
# -------------------------------------------------------------------------
debug:
name: "Debug"
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: >
Send persistent notifications for debugging automation behavior.
Shows raw event data, dispatched event kind, and the enable decision.
default: false
selector:
boolean:
# Queued mode to avoid dropping rapid events. A single cleanup-completion pass
# can fire up to 8 events (1 task_status + 7 consumables), so queue up to 10.
mode: queued
max: 10
# =============================================================================
# TRIGGERS
# =============================================================================
trigger:
# Cleaning job started or completed
- platform: event
event_type: dreame_vacuum_task_status
id: "task_status"
# Consumable reached end-of-life
- platform: event
event_type: dreame_vacuum_consumable
id: "consumable"
# Informational alert (job blocked by user settings)
- platform: event
event_type: dreame_vacuum_information
id: "information"
# Dismissible device warning
- platform: event
event_type: dreame_vacuum_warning
id: "warning"
# Device fault
- platform: event
event_type: dreame_vacuum_error
id: "error"
# =============================================================================
# VARIABLES
# =============================================================================
variables:
# Bumped whenever event-handling logic changes; surfaced in debug output
# so users can confirm which revision is running.
blueprint_version: "1.2.1"
# ---------------------------------------------------------------------------
# Input References
# ---------------------------------------------------------------------------
vacuum_entity: !input vacuum_entity
enable_debug_notifications: !input enable_debug_notifications
# Event toggles
enable_cleaning_started: !input enable_cleaning_started
enable_cleaning_completed: !input enable_cleaning_completed
enable_consumable: !input enable_consumable
enable_warning: !input enable_warning
enable_error: !input enable_error
enable_information: !input enable_information
# Filter lists (lists of strings)
warning_codes_ignore: !input warning_codes_ignore
error_codes_ignore: !input error_codes_ignore
information_codes_ignore: !input information_codes_ignore
# Message templates
message_cleaning_started_template: !input message_cleaning_started
message_cleaning_completed_template: !input message_cleaning_completed
message_consumable_template: !input message_consumable
message_warning_template: !input message_warning
message_error_template: !input message_error
message_information_template: !input message_information
# Localization overrides (dicts; default to empty mappings)
label_overrides: !input label_overrides
code_label_overrides: !input code_label_overrides
# ---------------------------------------------------------------------------
# Vacuum Info
# ---------------------------------------------------------------------------
# Prefer the device name (clean, single) over the entity's friendly_name.
# The Dreame integration names the vacuum entity " <device name>" (leading
# space) which Home Assistant composes into the friendly_name, so
# friendly_name renders the device name twice (e.g. "Z10 Pro Z10 Pro").
# Use the device's user-set/original name; fall back to friendly_name, then
# the entity_id.
vacuum_name: >
{%- set did = device_id(vacuum_entity) -%}
{%- set dname = (device_attr(did, 'name_by_user') or device_attr(did, 'name')) if did else none -%}
{{ dname if dname else (state_attr(vacuum_entity, 'friendly_name') | default(vacuum_entity, true)) }}
# ---------------------------------------------------------------------------
# Event Data (flat structure — fields are directly on trigger.event.data)
# ---------------------------------------------------------------------------
event_entity_id: "{{ trigger.event.data.entity_id | default('') }}"
# ---------------------------------------------------------------------------
# Friendly label maps
# ---------------------------------------------------------------------------
# The integration emits cleaning_mode and status as raw UPPER_SNAKE enum
# names (e.g. SWEEPING_AND_MOPPING, SEGMENT_CLEANING). These maps translate
# them to readable text. Lookup order at the call site is:
# user label_overrides -> built-in map below -> generic humanizer.
cleaning_mode_labels:
UNKNOWN: "Unknown"
SWEEPING: "Sweeping"
MOPPING: "Mopping"
SWEEPING_AND_MOPPING: "Sweeping & Mopping"
status_labels:
UNKNOWN: "Unknown"
IDLE: "Idle"
PAUSED: "Paused"
CLEANING: "Cleaning"
BACK_HOME: "Returning to dock"
PART_CLEANING: "Spot cleaning"
FOLLOW_WALL: "Following wall"
CHARGING: "Charging"
OTA: "Updating firmware"
FCT: "Factory check"
WIFI_SET: "Wi-Fi setup"
POWER_OFF: "Powered off"
FACTORY: "Factory mode"
ERROR: "Error"
REMOTE_CONTROL: "Remote control"
SLEEPING: "Sleeping"
SELF_TEST: "Self test"
FACTORY_FUNCION_TEST: "Factory function test"
STANDBY: "Standby"
SEGMENT_CLEANING: "Room cleaning"
ZONE_CLEANING: "Zone cleaning"
SPOT_CLEANING: "Spot cleaning"
FAST_MAPPING: "Mapping"
MONITOR_CRUISE: "Patrolling"
MONITOR_SPOT: "Spot monitoring"
SUMMON_CLEAN: "Summon clean"
# ---------------------------------------------------------------------------
# Task status fields
# ---------------------------------------------------------------------------
# Raw enum names as fired by the integration.
task_cleaning_mode_raw: "{{ trigger.event.data.cleaning_mode | default('UNKNOWN') }}"
task_status_raw: "{{ trigger.event.data.status | default('UNKNOWN') }}"
# Friendly values: override -> built-in map -> generic humanizer fallback.
task_cleaning_mode: "{{ (label_overrides or {}).get(task_cleaning_mode_raw, cleaning_mode_labels.get(task_cleaning_mode_raw, task_cleaning_mode_raw | replace('_', ' ') | title)) }}"
task_status_value: "{{ (label_overrides or {}).get(task_status_raw, status_labels.get(task_status_raw, task_status_raw | replace('_', ' ') | title)) }}"
# Coerce to a real bool so the started/completed dispatch stays correct.
task_completed: "{{ trigger.event.data.completed | default(false) | bool(false) }}"
task_cleaned_area: "{{ trigger.event.data.cleaned_area | default(0) }}"
task_cleaning_time: "{{ trigger.event.data.cleaning_time | default(0) }}"
# ---------------------------------------------------------------------------
# Consumable fields
# ---------------------------------------------------------------------------
consumable_name: "{{ (label_overrides or {}).get(trigger.event.data.consumable | default('unknown'), (trigger.event.data.consumable | default('unknown')) | replace('_', ' ') | title) }}"
# ---------------------------------------------------------------------------
# Warning fields
# ---------------------------------------------------------------------------
# Most warnings carry a short English description; the temporary-map events
# instead carry a bare snake id or a markdown blob. Humanize a bare id (and
# honor overrides) while leaving descriptive text / markdown untouched.
warning_description: >
{%- set w = trigger.event.data.warning | default('unknown') -%}
{%- if w and ' ' not in w and '#' not in w -%}{{ (label_overrides or {}).get(w, w | replace('_', ' ') | title) }}{%- else -%}{{ w }}{%- endif -%}
warning_code: "{{ trigger.event.data.code | default('') }}"
# ---------------------------------------------------------------------------
# Error fields
# ---------------------------------------------------------------------------
error_description: "{{ trigger.event.data.error | default('unknown') }}"
error_code: "{{ trigger.event.data.code | default('') }}"
# ---------------------------------------------------------------------------
# Code label (optional, localized)
# ---------------------------------------------------------------------------
# Numeric warning/error code as fired (empty for code-less events). code_name
# is the localized label for that code, exposed as {code_name}: a user override
# if present, otherwise the integration's own English description, otherwise
# empty. No built-in numeric code table is shipped, so there is no risk of
# stale labels across integration versions.
event_code: "{{ trigger.event.data.code | default('') }}"
code_name: >
{%- set lbl = (code_label_overrides or {}).get(event_code, (code_label_overrides or {}).get(event_code | string, '')) -%}
{%- if lbl -%}{{ lbl }}
{%- elif trigger.id == 'warning' -%}{{ warning_description }}
{%- elif trigger.id == 'error' -%}{{ error_description }}
{%- endif -%}
# ---------------------------------------------------------------------------
# Information fields
# ---------------------------------------------------------------------------
information_description: "{{ (label_overrides or {}).get(trigger.event.data.information | default('unknown'), (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title) }}"
# Information events carry a type id (e.g. dust_collection), never a numeric
# code — this raw id drives the Information ignore filter.
information_code: "{{ trigger.event.data.information | default('') }}"
# ---------------------------------------------------------------------------
# Event Dispatch
# ---------------------------------------------------------------------------
# Map the raw trigger to a single logical event kind. Keeping this in one
# place avoids the duplicated condition logic that the old multi-branch
# `choose` had.
event_kind: >
{%- if trigger.id == 'task_status' and not task_completed -%}cleaning_started
{%- elif trigger.id == 'task_status' and task_completed -%}cleaning_completed
{%- elif trigger.id == 'consumable' -%}consumable
{%- elif trigger.id == 'warning' -%}warning
{%- elif trigger.id == 'error' -%}error
{%- elif trigger.id == 'information' -%}information
{%- else -%}none
{%- endif -%}
# Whether this event should produce a notification, given user toggles
# and any per-code filter lists. Coerce with `| bool(false)` at the
# consumer because folded scalars can render as the string "True"/"False".
# The warning branch also drops the integration's spurious "replace_temporary_map"
# clear event (fired with no code, including on every restart).
event_enabled: >
{%- if event_kind == 'cleaning_started' -%}{{ enable_cleaning_started }}
{%- elif event_kind == 'cleaning_completed' -%}{{ enable_cleaning_completed }}
{%- elif event_kind == 'consumable' -%}{{ enable_consumable }}
{%- elif event_kind == 'warning' -%}{{ enable_warning and (trigger.event.data.warning | default('')) != 'replace_temporary_map' and (warning_code | string) not in warning_codes_ignore }}
{%- elif event_kind == 'error' -%}{{ enable_error and (error_code | string) not in error_codes_ignore }}
{%- elif event_kind == 'information' -%}{{ enable_information and (information_code | string) not in information_codes_ignore }}
{%- else -%}False
{%- endif -%}
# Pick the per-event message template.
message_template: >
{%- if event_kind == 'cleaning_started' -%}{{ message_cleaning_started_template }}
{%- elif event_kind == 'cleaning_completed' -%}{{ message_cleaning_completed_template }}
{%- elif event_kind == 'consumable' -%}{{ message_consumable_template }}
{%- elif event_kind == 'warning' -%}{{ message_warning_template }}
{%- elif event_kind == 'error' -%}{{ message_error_template }}
{%- elif event_kind == 'information' -%}{{ message_information_template }}
{%- else -%}{%- endif -%}
# Render the message. Placeholders that don't apply to this event are
# absent from the chosen template, so `replace()` is a no-op for them.
# {vacuum_name} is substituted last (it is the only user-controlled value)
# so a friendly name containing a literal token cannot be re-expanded. The
# final regex strips an empty "(label: )" parenthetical in any language left
# by a code-less event, e.g. "(code: )" or "(код предупреждения: )".
message: >
{%- set code_for_event = warning_code if event_kind == 'warning'
else (error_code if event_kind == 'error' else '') -%}
{%- set rendered = message_template
| replace('{cleaning_mode}', task_cleaning_mode)
| replace('{status}', task_status_value)
| replace('{cleaned_area}', task_cleaned_area | string)
| replace('{cleaning_time}', task_cleaning_time | string)
| replace('{consumable}', consumable_name)
| replace('{warning}', warning_description)
| replace('{error}', error_description)
| replace('{information}', information_description)
| replace('{code_name}', code_name)
| replace('{code}', code_for_event | string)
| replace('{vacuum_name}', vacuum_name) -%}
{{ rendered | regex_replace('\\s*\\(\\s*[^():]*:\\s*\\)', '') }}
# =============================================================================
# CONDITIONS
# =============================================================================
condition:
# Only process events from the configured vacuum. The integration fires every
# event with an `entity_id` derived from the device NAME via generate_entity_id()
# — events never carry a `device_id`, so matching is entity_id based.
# generate_entity_id() may append a purely numeric suffix (e.g. `_2`) when the
# base id is already taken, so we accept the configured entity_id exactly, or
# followed by a purely-numeric suffix, and reject non-numeric suffixes (e.g. `_pro`).
- condition: template
value_template: >
{%- if event_entity_id == '' -%}
false
{%- elif event_entity_id == vacuum_entity -%}
true
{%- elif event_entity_id.startswith(vacuum_entity ~ '_') -%}
{{ event_entity_id[(vacuum_entity | length) + 1:].isdigit() }}
{%- else -%}
false
{%- endif -%}
# =============================================================================
# ACTIONS
# =============================================================================
action:
# ---------------------------------------------------------------------------
# Debug Logging
# ---------------------------------------------------------------------------
- if:
- condition: template
value_template: "{{ enable_debug_notifications }}"
then:
- action: persistent_notification.create
data:
notification_id: "dreame_vacuum_debug_{{ vacuum_entity }}"
title: "Dreame Vacuum Debug — {{ vacuum_name }}"
message: >
**Blueprint version:** {{ blueprint_version }}
**Trigger:** {{ trigger.id }}
**Event kind:** {{ event_kind }}
**Enabled:** {{ event_enabled }}
**Entity:** {{ event_entity_id }}
**Vacuum:** {{ vacuum_name }}
**Event Data:** {{ trigger.event.data }}
# ---------------------------------------------------------------------------
# Send Notification
# ---------------------------------------------------------------------------
# Single dispatch — message and the enable decision are computed in the
# variables block above. We just gate on the resolved flags here.
- if:
- condition: template
value_template: "{{ event_kind != 'none' and (event_enabled | bool(false)) }}"
then:
- action: notify.send_message
target:
entity_id: !input notify_targets
data:
message: "{{ message }}"