From c8ab66caf38720a15ce1da837ff24a2bca9492ba Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 21 Jun 2026 21:02:10 +0300 Subject: [PATCH] feat: translate Dreame Vacuum operation codes and add localization - Render cleaning_mode/status as friendly labels instead of raw UPPER_SNAKE enum names - Add optional label_overrides / code_label_overrides inputs and a Russian starter table (localization.ru.yaml) - Add {code_name} placeholder for warning/error codes (override -> integration text -> empty) - Fix non-functional information filter (match the information type id, not an absent code) - Suppress the spurious replace_temporary_map clear warning; humanize bare snake-id warnings - Remove dead device_id matching branch; strip empty '(label: )' parenthetical (any language) - Robustness: queue max 10, task_completed bool coercion, notify target via !input, if/then actions --- Common/Dreame Vacuum/README.md | 141 +++++++++++-- Common/Dreame Vacuum/blueprint.yaml | 244 ++++++++++++++++------ Common/Dreame Vacuum/localization.ru.yaml | 152 ++++++++++++++ manifest.json | 2 +- 4 files changed, 459 insertions(+), 80 deletions(-) create mode 100644 Common/Dreame Vacuum/localization.ru.yaml diff --git a/Common/Dreame Vacuum/README.md b/Common/Dreame Vacuum/README.md index 49be4ca..ec7c066 100644 --- a/Common/Dreame Vacuum/README.md +++ b/Common/Dreame Vacuum/README.md @@ -8,8 +8,10 @@ Sends customizable notifications for Dreame vacuum events. Requires the [Dreame - Consumable end-of-life alerts (brush, filter, mop pad, sensor, etc.) - Device warning and error notifications - Informational alerts (e.g., action blocked by Do Not Disturb) +- Friendly, readable labels for cleaning mode and status (raw enum names are translated) +- Optional localization: override any label, and map numeric codes to your own text - Individual toggle for each event type -- Per-code filter lists for silencing routine warnings, errors, or info messages +- Per-code/per-type filter lists for silencing routine warnings, errors, or info messages - Customizable message templates - Multiple notification targets @@ -25,12 +27,16 @@ The blueprint listens to events fired by the Dreame Vacuum integration: | `dreame_vacuum_error` | Device fault | | `dreame_vacuum_information` | Action blocked by user settings | -Events are filtered by the configured vacuum: +### Matching 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. +The integration fires every event with an `entity_id` that it derives from the vacuum's **device name** (via `generate_entity_id()`); events do **not** carry a `device_id`. Matching therefore accepts: -This avoids false positives when two vacuums share an entity_id prefix (e.g., `vacuum.dreame_x10` vs. `vacuum.dreame_x10_pro`). +1. The event `entity_id` exactly equal to the configured entity. +2. The configured entity followed by a purely numeric suffix (e.g. `vacuum.dreame_x10_2`) — `generate_entity_id()` adds such a suffix when the base id is already taken. + +Non-numeric suffixes (e.g. `_pro`) are rejected, so `vacuum.dreame_x10` will not match `vacuum.dreame_x10_pro`. + +> **Note:** because the fired `entity_id` is derived from the device name, if you rename the vacuum's entity_id in Home Assistant so it no longer matches the auto-generated id, matching can fail and notifications stop. Keep the integration-generated entity_id (or rename the *device* instead of the entity) for reliable matching. ## Configuration @@ -39,12 +45,15 @@ This avoids false positives when two vacuums share an entity_id prefix (e.g., `v | **Vacuum Entity** | The Dreame vacuum entity to monitor | | **Notification Targets** | One or more `notify` entities | | **Event Toggles** | Enable/disable each event type independently | -| **Filtering** | Lists of warning/error/information codes to silence | +| **Filtering** | Lists of warning/error codes and information types to silence | | **Message Templates** | Customizable message for each event type | +| **Localization** | Optional label and code translation tables (see [Localization](#localization)) | ### 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. +- **Warning Codes / Error Codes to Ignore** — compared against the event's numeric `code`. Enter the code as text (e.g. `68`). +- **Information Types to Ignore** — information events carry a *type id*, not a numeric code; enter the id (`dust_collection`, `cleaning_paused`). +- The two temporary-map warnings (new-map / replace-temporary-map) carry **no** code. The spurious "map cleared" warning is suppressed automatically; the genuine "new map generated" warning can be filtered by its text if needed. ## Message Template Variables @@ -53,16 +62,16 @@ Each filter input is a list of codes (as strings) that should not produce a noti | Variable | Description | | --- | --- | | `{vacuum_name}` | Friendly name of the vacuum | -| `{cleaning_mode}` | Cleaning mode (e.g., sweeping, mopping) | -| `{status}` | Current status | +| `{cleaning_mode}` | Cleaning mode, friendly-formatted (e.g., Sweeping, Mopping, Sweeping & Mopping) | +| `{status}` | Current status, friendly-formatted (e.g., Cleaning, Room cleaning) | ### Cleaning Completed | Variable | Description | | --- | --- | | `{vacuum_name}` | Friendly name of the vacuum | -| `{cleaning_mode}` | Cleaning mode used | -| `{status}` | Final status | +| `{cleaning_mode}` | Cleaning mode used, friendly-formatted | +| `{status}` | Final status, friendly-formatted | | `{cleaned_area}` | Area cleaned (m²) | | `{cleaning_time}` | Cleaning duration (minutes) | @@ -78,16 +87,18 @@ Each filter input is a list of codes (as strings) that should not produce a noti | Variable | Description | | --- | --- | | `{vacuum_name}` | Friendly name of the vacuum | -| `{warning}` | Warning description | -| `{code}` | Warning code | +| `{warning}` | Warning description (already human-readable English from the integration) | +| `{code}` | Numeric warning code (empty for the temporary-map warning) | +| `{code_name}` | Optional label for the code — empty unless you fill the code table (see [Localization](#localization)) | ### Error | Variable | Description | | --- | --- | | `{vacuum_name}` | Friendly name of the vacuum | -| `{error}` | Error description | -| `{code}` | Error code | +| `{error}` | Error description (already human-readable English from the integration) | +| `{code}` | Numeric error code | +| `{code_name}` | Optional label for the code — empty unless you fill the code table (see [Localization](#localization)) | ### Information @@ -95,12 +106,108 @@ Each filter input is a list of codes (as strings) that should not produce a noti | --- | --- | | `{vacuum_name}` | Friendly name of the vacuum | | `{information}` | Information message (e.g., Dust Collection, Cleaning Paused) | -| `{code}` | Information code | + +> The default warning/error templates show `(code: {code})`. When an event has no code, the blueprint automatically strips the empty `(code: )` parenthetical. + +## Friendly Labels + +`{cleaning_mode}` and `{status}` arrive from the integration as raw `UPPER_SNAKE_CASE` enum names; the blueprint translates them to readable text. Any value not in the tables below falls back to a generic humanizer (underscores → spaces, capitalized). All labels can be overridden — see [Localization](#localization). + +### Cleaning Mode (`{cleaning_mode}`) + +| Raw enum | Friendly label | +| --- | --- | +| `UNKNOWN` | Unknown | +| `SWEEPING` | Sweeping | +| `MOPPING` | Mopping | +| `SWEEPING_AND_MOPPING` | Sweeping & Mopping | + +### Status (`{status}`) + +| Raw enum | Friendly label | +| --- | --- | +| `IDLE` | Idle | +| `PAUSED` | Paused | +| `CLEANING` | Cleaning | +| `BACK_HOME` | Returning to dock | +| `PART_CLEANING` | Spot cleaning | +| `FOLLOW_WALL` | Following wall | +| `CHARGING` | Charging | +| `OTA` | Updating firmware | +| `WIFI_SET` | Wi-Fi setup | +| `POWER_OFF` | Powered off | +| `ERROR` | Error | +| `REMOTE_CONTROL` | Remote control | +| `SLEEPING` | Sleeping | +| `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 | + +(Also mapped: `FCT`, `FACTORY`, `SELF_TEST`, `FACTORY_FUNCION_TEST` — diagnostic states.) + +### Consumable (`{consumable}`) and Information (`{information}`) + +| Raw id | Friendly label | +| --- | --- | +| `main_brush` | Main Brush | +| `side_brush` | Side Brush | +| `filter` | Filter | +| `sensor` | Sensor | +| `mop_pad` | Mop Pad | +| `silver_ion` | Silver Ion | +| `detergent` | Detergent | +| `dust_collection` | Dust Collection | +| `cleaning_paused` | Cleaning Paused | + +## Localization + +Two optional inputs (under **Localization**) let you relabel or translate any value without editing the blueprint. Both are entered as YAML key/value pairs (the object selector). + +**Label Overrides** — keys are the raw values the integration emits (cleaning-mode/status are `UPPER_SNAKE` enum names; consumable/information are lower-case ids). Applied to `{cleaning_mode}`, `{status}`, `{consumable}`, and `{information}`: + +```yaml +SWEEPING_AND_MOPPING: Подметание и мытьё +CLEANING: Уборка +BACK_HOME: Возврат на базу +dust_collection: Очистка контейнера +``` + +**Warning/Error Code Labels** — keys are numeric codes (unquoted); the value is exposed as `{code_name}`. Leave empty to omit `{code_name}`: + +```yaml +68: Снять швабру +47: Робот застрял +``` + +Lookup order for every value is: your override → built-in friendly label → generic humanizer. + +A ready-to-paste **Russian** starter table (all statuses, modes, consumables, information types, and warning/error codes) is provided in [localization.ru.yaml](localization.ru.yaml) — copy each section into the matching Localization input. To show fully Russian warning/error text, reference `{code_name}` in your warning/error templates instead of the English `{warning}`/`{error}`. + +> No built-in numeric-code → name table is shipped, because code meanings can differ between integration versions. The `{warning}` / `{error}` text already comes from your installed integration (so it is always correct); use `{code_name}` only if you want a short, localized tag for specific codes. + +### Warning vs. Error Codes + +The integration classifies a fault as a dismissible **warning** only for the codes below; every other fault with a positive code (except battery-low) is reported as an **error** and carries a numeric `{code}` you can paste into **Error Codes to Ignore**. + +| Code | Name | +| --- | --- | +| 47 | Blocked | +| 68 | Remove mop | +| 70 | Mop removed | +| 71 | Mop pad stopped rotating | +| 72 | Mop pad stopped rotating | +| 107 | Water tank dry | +| 114 | Clean mop pad | ## 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. +- `notify.send_message` requires Home Assistant 2024.7 or newer. It accepts only a `message` (no title/tag); put any emphasis directly in the message text. ## Debug Mode diff --git a/Common/Dreame Vacuum/blueprint.yaml b/Common/Dreame Vacuum/blueprint.yaml index 570502b..a98553e 100644 --- a/Common/Dreame Vacuum/blueprint.yaml +++ b/Common/Dreame Vacuum/blueprint.yaml @@ -123,9 +123,11 @@ blueprint: multiple: true information_codes_ignore: - name: Information Codes to Ignore + name: Information Types to Ignore description: > - List of information codes to silence (one entry per code, as text). + 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: @@ -173,7 +175,7 @@ blueprint: name: "Warning Message" description: > Message sent for device warnings. - Variables: `{vacuum_name}`, `{warning}`, `{code}` + Variables: `{vacuum_name}`, `{warning}`, `{code}`, `{code_name}` default: "⚠️ {vacuum_name} warning: {warning} (code: {code})." selector: text: @@ -183,7 +185,7 @@ blueprint: name: "Error Message" description: > Message sent for device errors. - Variables: `{vacuum_name}`, `{error}`, `{code}` + Variables: `{vacuum_name}`, `{error}`, `{code}`, `{code_name}` default: "❌ {vacuum_name} error: {error} (code: {code})." selector: text: @@ -193,12 +195,48 @@ blueprint: name: "Information Message" description: > Message sent for informational alerts. - Variables: `{vacuum_name}`, `{information}`, `{code}` + 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 # ------------------------------------------------------------------------- @@ -215,9 +253,10 @@ blueprint: selector: boolean: -# Queued mode to avoid dropping rapid events +# 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: 5 +max: 10 # ============================================================================= # TRIGGERS @@ -254,13 +293,12 @@ trigger: variables: # Bumped whenever event-handling logic changes; surfaced in debug output # so users can confirm which revision is running. - blueprint_version: "1.1.1" + blueprint_version: "1.2.0" # --------------------------------------------------------------------------- # Input References # --------------------------------------------------------------------------- vacuum_entity: !input vacuum_entity - notify_targets: !input notify_targets enable_debug_notifications: !input enable_debug_notifications # Event toggles @@ -284,39 +322,119 @@ variables: 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 # --------------------------------------------------------------------------- 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_entity_id: "{{ trigger.event.data.entity_id | default('') }}" - event_device_id: "{{ trigger.event.data.device_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 - task_cleaning_mode: "{{ trigger.event.data.cleaning_mode | default('unknown') }}" - task_status_value: "{{ trigger.event.data.status | default('unknown') }}" - task_completed: "{{ trigger.event.data.completed | default(false) }}" + # --------------------------------------------------------------------------- + # 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: "{{ (trigger.event.data.consumable | default('unknown')) | replace('_', ' ') | title }}" + # --------------------------------------------------------------------------- + consumable_name: "{{ (label_overrides or {}).get(trigger.event.data.consumable | default('unknown'), (trigger.event.data.consumable | default('unknown')) | replace('_', ' ') | title) }}" + # --------------------------------------------------------------------------- # Warning fields - warning_description: "{{ trigger.event.data.warning | default('unknown') }}" + # --------------------------------------------------------------------------- + # 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: "{{ (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title }}" - information_code: "{{ trigger.event.data.code | default('') }}" + # --------------------------------------------------------------------------- + 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 @@ -337,11 +455,13 @@ variables: # 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 (warning_code | string) not in warning_codes_ignore }} + {%- 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 @@ -359,12 +479,14 @@ variables: # 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 (information_code if event_kind == 'information' else '')) -%} - {{ message_template - | replace('{vacuum_name}', vacuum_name) + 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) @@ -373,24 +495,25 @@ variables: | replace('{warning}', warning_description) | replace('{error}', error_description) | replace('{information}', information_description) - | replace('{code}', code_for_event | string) }} + | 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. - # Prefer device_id matching when the event payload carries it (most - # reliable across firmware revisions and avoids prefix collisions when - # 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`). + # 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_device_id != '' and vacuum_device != '' -%} - {{ event_device_id == vacuum_device }} + {%- if event_entity_id == '' -%} + false {%- elif event_entity_id == vacuum_entity -%} true {%- elif event_entity_id.startswith(vacuum_entity ~ '_') -%} @@ -407,37 +530,34 @@ action: # --------------------------------------------------------------------------- # Debug Logging # --------------------------------------------------------------------------- - - choose: - - conditions: - - condition: template - value_template: "{{ enable_debug_notifications }}" - sequence: - - 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 }} - **Device:** {{ event_device_id }} - **Vacuum:** {{ vacuum_name }} - **Event Data:** {{ trigger.event.data }} + - 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. - - choose: - - conditions: - - condition: template - value_template: "{{ event_kind != 'none' and (event_enabled | bool(false)) }}" - sequence: - - action: notify.send_message - target: - entity_id: "{{ notify_targets }}" - data: - message: "{{ message }}" + - 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 }}" diff --git a/Common/Dreame Vacuum/localization.ru.yaml b/Common/Dreame Vacuum/localization.ru.yaml new file mode 100644 index 0000000..1f1d0aa --- /dev/null +++ b/Common/Dreame Vacuum/localization.ru.yaml @@ -0,0 +1,152 @@ +# Russian (русский) starter localization for the Dreame Vacuum Notifications blueprint. +# +# HOW TO USE +# When configuring the automation from the blueprint, open the "Localization" +# section and paste: +# * the mapping under `label_overrides:` -> into the "Label Overrides" input +# * the mapping under `code_label_overrides:` -> into the "Warning/Error Code Labels" input +# (Paste the key/value lines themselves; the object selector takes the mapping.) +# +# {cleaning_mode} / {status} / {consumable} / {information} use label_overrides. +# {code_name} uses code_label_overrides. To show fully Russian warning/error +# text, use {code_name} in your warning/error templates instead of the +# integration-supplied English {warning}/{error}, e.g.: +# message_warning: "⚠️ {vacuum_name}: {code_name} (код {code})." +# +# Tweak any wording to taste. Anything you leave out falls back to the built-in +# English label, then to a generic humanizer. + +label_overrides: + # --- Cleaning mode ({cleaning_mode}) --- + UNKNOWN: Неизвестно + SWEEPING: Подметание + MOPPING: Мытьё + SWEEPING_AND_MOPPING: Подметание и мытьё + + # --- Status ({status}) --- + IDLE: Ожидание + PAUSED: Пауза + CLEANING: Уборка + BACK_HOME: Возврат на базу + PART_CLEANING: Точечная уборка + FOLLOW_WALL: Уборка вдоль стен + CHARGING: Зарядка + OTA: Обновление прошивки + FCT: Заводская проверка + WIFI_SET: Настройка Wi-Fi + POWER_OFF: Выключен + FACTORY: Заводской режим + ERROR: Ошибка + REMOTE_CONTROL: Ручное управление + SLEEPING: Сон + SELF_TEST: Самодиагностика + FACTORY_FUNCION_TEST: Заводской тест функций + STANDBY: Режим ожидания + SEGMENT_CLEANING: Уборка комнат + ZONE_CLEANING: Уборка зон + SPOT_CLEANING: Точечная уборка + FAST_MAPPING: Построение карты + MONITOR_CRUISE: Патрулирование + MONITOR_SPOT: Видеонаблюдение + SUMMON_CLEAN: Уборка по вызову + + # --- Consumables ({consumable}) --- + main_brush: Основная щётка + side_brush: Боковая щётка + filter: Фильтр + secondary_filter: Вторичный фильтр + sensor: Датчики + mop_pad: Насадка для мытья + silver_ion: Серебряный ионизатор + detergent: Моющее средство + + # --- Information ({information}) --- + dust_collection: Очистка пылесборника + cleaning_paused: Уборка приостановлена + +code_label_overrides: + # Numeric warning/error code -> short Russian label ({code_name}). + # Warnings: 47, 68, 70, 71, 72, 107, 114. Everything else is an error. + 1: Колёса вывешены + 2: Ошибка датчика обрыва + 3: Заклинило датчик столкновения + 4: Робот наклонён + 5: Заклинило датчик столкновения + 6: Колёса вывешены + 7: Ошибка оптического датчика + 8: Не установлен пылесборник + 9: Не установлен бак для воды + 10: Бак для воды пуст + 11: Фильтр влажный или забит + 12: Намотка на основную щётку + 13: Намотка на боковую щётку + 14: Фильтр влажный или забит + 15: Заблокировано левое колесо + 16: Заблокировано правое колесо + 17: Робот застрял (не может повернуть) + 18: Робот застрял (не может ехать) + 19: Не найдена база + 21: Ошибка зарядки + 23: Внутренняя ошибка + 24: Ошибка датчика навигации + 25: Ошибка датчика перемещения + 26: Ошибка оптического датчика + 27: Помеха ИК-датчику + 28: На базу не подаётся питание + 29: Ошибка температуры батареи + 30: Ошибка датчика вентилятора + 31: Заблокировано левое колесо + 32: Заблокировано правое колесо + 33: Ошибка акселерометра + 34: Ошибка гироскопа + 35: Ошибка гироскопа + 36: Ошибка левого магнитного датчика + 37: Ошибка правого магнитного датчика + 38: Ошибка датчика потока + 39: Ошибка ИК-датчика + 40: Ошибка камеры + 41: Сильное магнитное поле + 42: Ошибка водяного насоса + 43: Ошибка часов (RTC) + 44: Внутренняя ошибка + 45: Внутренняя ошибка + 46: Внутренняя ошибка + 47: Маршрут заблокирован + 48: Ошибка лазерного дальномера + 49: Заклинило бампер лидара + 50: Ошибка водяного насоса + 51: Фильтр влажный или забит + 54: Ошибка краевого датчика + 55: Под роботом обнаружен ковёр + 56: Ошибка датчика обхода препятствий + 57: Ошибка краевого датчика + 58: Ошибка ультразвукового датчика + 59: Запретная зона или виртуальная стена + 61: Не удаётся достичь зоны + 62: Не удаётся достичь зоны + 63: Маршрут заблокирован + 64: Маршрут заблокирован + 65: Робот в запретной зоне + 66: Робот в запретной зоне + 67: Робот в запретной зоне + 68: Снимите и промойте швабру + 69: Отсоединилась насадка для мытья + 70: Отсоединилась насадка для мытья + 71: Насадка для мытья не вращается + 72: Насадка для мытья не вращается + 101: Мешок для пыли заполнен или забит воздуховод + 102: Крышка базы открыта или нет мешка + 103: Крышка базы открыта или нет мешка + 104: Мешок для пыли заполнен или забит воздуховод + 105: Не установлен бак чистой воды + 106: Бак грязной воды полон или не установлен + 107: Мало чистой воды + 108: Бак грязной воды полон или не установлен + 109: Засор бака грязной воды + 110: Ошибка насоса грязной воды + 111: Неправильно установлен лоток мойки + 112: Очистите лоток мойки + 114: Очистите лоток мойки швабры + 116: Долейте чистую воду + 118: Бак грязной воды переполнен + 119: Высокий уровень воды в лотке мойки diff --git a/manifest.json b/manifest.json index 90bb067..4af42bf 100644 --- a/manifest.json +++ b/manifest.json @@ -1,3 +1,3 @@ { - "version": "2.13.0" + "version": "2.14.0" }