# 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 " " (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 }}"