fix: remove notification title from Dreame Vacuum to avoid duplicated device name

The `notification_title` input defaulted to `{vacuum_name}`, which caused the
device name to appear twice (once in the title, once in the message body that
already starts with `{vacuum_name}`). Drop the title input and revert to
message-only notifications, matching the original behavior.
This commit is contained in:
2026-05-07 22:11:21 +03:00
parent 3e98e14882
commit a52cffa062
3 changed files with 157 additions and 121 deletions
+21 -4
View File
@@ -1,6 +1,6 @@
# Dreame Vacuum Notifications # Dreame Vacuum Notifications
Sends customizable notifications for Dreame vacuum events. Requires the [Dreame Vacuum](https://github.com/Tasshack/dreame-vacuum) integration. Sends customizable notifications for Dreame vacuum events. Requires the [Dreame Vacuum](https://github.com/Tasshack/dreame-vacuum) integration and Home Assistant 2024.7+ (`notify.send_message` action).
## Features ## Features
@@ -9,7 +9,8 @@ Sends customizable notifications for Dreame vacuum events. Requires the [Dreame
- Device warning and error notifications - Device warning and error notifications
- Informational alerts (e.g., action blocked by Do Not Disturb) - Informational alerts (e.g., action blocked by Do Not Disturb)
- Individual toggle for each event type - Individual toggle for each event type
- Customizable message templates with variable substitution - Per-code filter lists for silencing routine warnings, errors, or info messages
- Customizable message templates
- Multiple notification targets - Multiple notification targets
## How It Works ## How It Works
@@ -24,7 +25,12 @@ The blueprint listens to events fired by the Dreame Vacuum integration:
| `dreame_vacuum_error` | Device fault | | `dreame_vacuum_error` | Device fault |
| `dreame_vacuum_information` | Action blocked by user settings | | `dreame_vacuum_information` | Action blocked by user settings |
Events are filtered by the configured vacuum entity, so only the selected vacuum triggers notifications. Events are filtered by the configured vacuum:
1. If the event payload includes `device_id`, it must match the device of the configured vacuum entity. This is the most reliable match.
2. Otherwise the entity_id in the event must equal the configured one, or equal it followed by a purely numeric suffix (e.g., `vacuum.dreame_x10_2`). The integration uses `generate_entity_id()`, so a numeric suffix can appear when the base entity_id is taken.
This avoids false positives when two vacuums share an entity_id prefix (e.g., `vacuum.dreame_x10` vs. `vacuum.dreame_x10_pro`).
## Configuration ## Configuration
@@ -33,8 +39,13 @@ Events are filtered by the configured vacuum entity, so only the selected vacuum
| **Vacuum Entity** | The Dreame vacuum entity to monitor | | **Vacuum Entity** | The Dreame vacuum entity to monitor |
| **Notification Targets** | One or more `notify` entities | | **Notification Targets** | One or more `notify` entities |
| **Event Toggles** | Enable/disable each event type independently | | **Event Toggles** | Enable/disable each event type independently |
| **Filtering** | Lists of warning/error/information codes to silence |
| **Message Templates** | Customizable message for each event type | | **Message Templates** | Customizable message for each event type |
### Filtering
Each filter input is a list of codes (as strings) that should not produce a notification. The blueprint compares the event's `code` field (cast to string) against the list. Use this to suppress noisy routine events while keeping critical ones.
## Message Template Variables ## Message Template Variables
### Cleaning Started ### Cleaning Started
@@ -84,10 +95,16 @@ Events are filtered by the configured vacuum entity, so only the selected vacuum
| --- | --- | | --- | --- |
| `{vacuum_name}` | Friendly name of the vacuum | | `{vacuum_name}` | Friendly name of the vacuum |
| `{information}` | Information message (e.g., Dust Collection, Cleaning Paused) | | `{information}` | Information message (e.g., Dust Collection, Cleaning Paused) |
| `{code}` | Information code |
## Notes
- The default messages contain emojis. Most modern notify integrations (Mobile App, Telegram, Discord) render them correctly; legacy SMS-based integrations may not.
- `notify.send_message` requires Home Assistant 2024.7 or newer.
## Debug Mode ## Debug Mode
Enable **Debug Notifications** in the Debug section to send persistent notifications with raw event data for troubleshooting. Enable **Debug Notifications** in the Debug section to send a persistent notification for every trigger. Each debug notification includes the blueprint version, dispatched event kind, and the enable decision — useful for confirming filters are doing what you expect. The notification is keyed per vacuum so debug entries from multiple vacuums do not overwrite each other.
## Author ## Author
+135 -116
View File
@@ -96,6 +96,41 @@ blueprint:
selector: selector:
boolean: 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 Codes to Ignore
description: >
List of information codes to silence (one entry per code, as text).
default: []
selector:
text:
multiple: true
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Message Templates # Message Templates
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -158,7 +193,7 @@ blueprint:
name: "Information Message" name: "Information Message"
description: > description: >
Message sent for informational alerts. Message sent for informational alerts.
Variables: `{vacuum_name}`, `{information}` Variables: `{vacuum_name}`, `{information}`, `{code}`
default: "️ {vacuum_name}: {information}." default: "️ {vacuum_name}: {information}."
selector: selector:
text: text:
@@ -175,7 +210,7 @@ blueprint:
name: Enable Debug Notifications name: Enable Debug Notifications
description: > description: >
Send persistent notifications for debugging automation behavior. Send persistent notifications for debugging automation behavior.
Shows raw event data and filtering decisions. Shows raw event data, dispatched event kind, and the enable decision.
default: false default: false
selector: selector:
boolean: boolean:
@@ -217,6 +252,10 @@ trigger:
# VARIABLES # VARIABLES
# ============================================================================= # =============================================================================
variables: variables:
# Bumped whenever event-handling logic changes; surfaced in debug output
# so users can confirm which revision is running.
blueprint_version: "1.1.1"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Input References # Input References
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -232,6 +271,11 @@ variables:
enable_error: !input enable_error enable_error: !input enable_error
enable_information: !input enable_information 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 templates
message_cleaning_started_template: !input message_cleaning_started message_cleaning_started_template: !input message_cleaning_started
message_cleaning_completed_template: !input message_cleaning_completed message_cleaning_completed_template: !input message_cleaning_completed
@@ -244,11 +288,13 @@ variables:
# Vacuum Info # Vacuum Info
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
vacuum_name: "{{ state_attr(vacuum_entity, 'friendly_name') | default(vacuum_entity) }}" vacuum_name: "{{ state_attr(vacuum_entity, 'friendly_name') | default(vacuum_entity) }}"
vacuum_device: "{{ device_id(vacuum_entity) | default('', true) }}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Event Data (flat structure — fields are directly on trigger.event.data) # Event Data (flat structure — fields are directly on trigger.event.data)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
event_entity_id: "{{ trigger.event.data.entity_id | default('') }}" event_entity_id: "{{ trigger.event.data.entity_id | default('') }}"
event_device_id: "{{ trigger.event.data.device_id | default('') }}"
# Task status fields # Task status fields
task_cleaning_mode: "{{ trigger.event.data.cleaning_mode | default('unknown') }}" task_cleaning_mode: "{{ trigger.event.data.cleaning_mode | default('unknown') }}"
@@ -270,19 +316,88 @@ variables:
# Information fields # Information fields
information_description: "{{ (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title }}" information_description: "{{ (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title }}"
information_code: "{{ trigger.event.data.code | 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".
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 (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.
message: >
{%- set code_for_event = warning_code if event_kind == 'warning'
else (error_code if event_kind == 'error'
else (information_code if event_kind == 'information' else '')) -%}
{{ message_template
| replace('{vacuum_name}', vacuum_name)
| 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}', code_for_event | string) }}
# ============================================================================= # =============================================================================
# CONDITIONS # CONDITIONS
# ============================================================================= # =============================================================================
condition: condition:
# Only process events from the configured vacuum entity. # Only process events from the configured vacuum.
# The Dreame Vacuum integration uses generate_entity_id() for the entity_id # Prefer device_id matching when the event payload carries it (most
# in event data, which may append a numeric suffix (e.g., _2) since the # reliable across firmware revisions and avoids prefix collisions when
# actual vacuum entity already occupies the base entity_id. # multiple Dreame vacuums share an entity_id stem).
# Fall back to entity_id matching: the integration uses generate_entity_id(),
# which may append a numeric suffix (e.g., `_2`) when the base entity_id
# is taken — so we accept the configured entity_id with an optional
# purely numeric suffix and reject non-numeric suffixes (e.g., `_pro`).
- condition: template - condition: template
value_template: > value_template: >
{{ event_entity_id == vacuum_entity {%- if event_device_id != '' and vacuum_device != '' -%}
or event_entity_id.startswith(vacuum_entity ~ '_') }} {{ event_device_id == vacuum_device }}
{%- 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 # ACTIONS
@@ -297,127 +412,31 @@ action:
- condition: template - condition: template
value_template: "{{ enable_debug_notifications }}" value_template: "{{ enable_debug_notifications }}"
sequence: sequence:
- service: persistent_notification.create - action: persistent_notification.create
data: data:
title: "Dreame Vacuum Debug" notification_id: "dreame_vacuum_debug_{{ vacuum_entity }}"
title: "Dreame Vacuum Debug — {{ vacuum_name }}"
message: > message: >
**Blueprint version:** {{ blueprint_version }}
**Trigger:** {{ trigger.id }} **Trigger:** {{ trigger.id }}
**Event kind:** {{ event_kind }}
**Enabled:** {{ event_enabled }}
**Entity:** {{ event_entity_id }} **Entity:** {{ event_entity_id }}
**Device:** {{ event_device_id }}
**Vacuum:** {{ vacuum_name }} **Vacuum:** {{ vacuum_name }}
**Event Data:** {{ trigger.event.data }} **Event Data:** {{ trigger.event.data }}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Send Notification Based on Event Type # Send Notification
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Single dispatch — message and the enable decision are computed in the
# variables block above. We just gate on the resolved flags here.
- choose: - choose:
# CASE 1: Cleaning Started
- conditions: - conditions:
- condition: template - condition: template
value_template: > value_template: "{{ event_kind != 'none' and (event_enabled | bool(false)) }}"
{{ trigger.id == 'task_status'
and not task_completed
and enable_cleaning_started }}
sequence: sequence:
- variables: - action: notify.send_message
message: >
{% set tpl = message_cleaning_started_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{cleaning_mode}', task_cleaning_mode)
| replace('{status}', task_status_value) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 2: Cleaning Completed
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'task_status'
and task_completed
and enable_cleaning_completed }}
sequence:
- variables:
message: >
{% set tpl = message_cleaning_completed_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| 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) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 3: Consumable Depleted
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'consumable' and enable_consumable }}
sequence:
- variables:
message: >
{% set tpl = message_consumable_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{consumable}', consumable_name) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 4: Warning
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'warning' and enable_warning }}
sequence:
- variables:
message: >
{% set tpl = message_warning_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{warning}', warning_description)
| replace('{code}', warning_code | string) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 5: Error
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'error' and enable_error }}
sequence:
- variables:
message: >
{% set tpl = message_error_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{error}', error_description)
| replace('{code}', error_code | string) }}
- service: notify.send_message
target:
entity_id: "{{ notify_targets }}"
data:
message: "{{ message }}"
# CASE 6: Information
- conditions:
- condition: template
value_template: >
{{ trigger.id == 'information' and enable_information }}
sequence:
- variables:
message: >
{% set tpl = message_information_template %}
{{ tpl | replace('{vacuum_name}', vacuum_name)
| replace('{information}', information_description) }}
- service: notify.send_message
target: target:
entity_id: "{{ notify_targets }}" entity_id: "{{ notify_targets }}"
data: data:
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "2.6.4" "version": "2.9.1"
} }