More changes to #1470 -

Refactor Dreame vacuum orchestration to implement continuous phased cleaning cycles. Update logic for room queue management and notifications, enhancing automation for idle periods and phase transitions.
This commit is contained in:
Carlo Costanzo
2025-12-09 13:14:00 -05:00
parent ddda9e6573
commit ea8d57393a
2 changed files with 106 additions and 144 deletions

View File

@@ -45,7 +45,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [lightning.yaml](lightning.yaml) | Blitzortung lightning counter monitoring with snoozeable push actions. | `sensor.blitzortung_lightning_counter`, `input_boolean.snooze_lightning`, notify engine actions | | [lightning.yaml](lightning.yaml) | Blitzortung lightning counter monitoring with snoozeable push actions. | `sensor.blitzortung_lightning_counter`, `input_boolean.snooze_lightning`, notify engine actions |
| [phynplus.yaml](phynplus.yaml) | Tie the Phyn Plus smart shutoff into HA notifications, automations, and valve overrides. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `script.notify_engine_two_button` | | [phynplus.yaml](phynplus.yaml) | Tie the Phyn Plus smart shutoff into HA notifications, automations, and valve overrides. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `script.notify_engine_two_button` |
| [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status and shed loads automatically when off-grid. | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `script.notify_engine` | | [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status and shed loads automatically when off-grid. | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `script.notify_engine` |
| [vacuum.yaml](vacuum.yaml) | Dreame (ex-Neato) vacuum orchestration with maintenance reminders and reset helpers. | `sensor.l10s_vacuum_task_status`, `sensor.l10s_vacuum_sensor_dirty_left`, `button.l10s_vacuum_reset_sensor` | | [vacuum.yaml](vacuum.yaml) | Dreame (ex-Neato) vacuum orchestration with continuous phased sweep/mop cycles. | `input_select.l10s_vacuum_phase`, `input_text.l10s_vacuum_room_queue`, `sensor.l10s_vacuum_task_status` |
| [hass_agent_homepc.yaml](hass_agent_homepc.yaml) | Mirrors PC lock/unlock state from HASS.Agent to the office lamp for instant desk presence cues. | `sensor.carlo_homepc_carlo_homepc_sessionstate`, `switch.office_lamp_switch` | | [hass_agent_homepc.yaml](hass_agent_homepc.yaml) | Mirrors PC lock/unlock state from HASS.Agent to the office lamp for instant desk presence cues. | `sensor.carlo_homepc_carlo_homepc_sessionstate`, `switch.office_lamp_switch` |
| [finance.yaml](finance.yaml) | Yahoo Finance sensor bundle for portfolio glances and Lovelace cards. | `sensor.tsla`, `sensor.aapl`, `sensor.amzn`, `sensor.msft` | | [finance.yaml](finance.yaml) | Yahoo Finance sensor bundle for portfolio glances and Lovelace cards. | `sensor.tsla`, `sensor.aapl`, `sensor.amzn`, `sensor.msft` |
@@ -60,8 +60,8 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
![Nest Climate Control](../www/custom_ui/floorplan/images/branding/Nest_Climate_Control.png) ![Nest Climate Control](../www/custom_ui/floorplan/images/branding/Nest_Climate_Control.png)
### Dreame vacuum automations ### Dreame vacuum automations
- Logic lives in [vacuum.yaml](vacuum.yaml): weekday sweeping/weekend mopping, room-queue segment cleaning that pauses/docks on arrival, resumes after charging, and per-room notifications/briefing summaries. - Logic lives in [vacuum.yaml](vacuum.yaml): continuous four-phase loop (sweep main, sweep baths, mop main, mop baths) driven by `input_select.l10s_vacuum_phase` and `input_text.l10s_vacuum_room_queue`, with per-room notifications and automatic reseeding between phases.
- Uses the Dreame HACS integration with map-based segments and daily resets to cover the whole house without repeating rooms. - Uses the Dreame HACS integration with segment IDs to enforce bathrooms last in each sweep/mop pass, dock on arrival, and auto-run if idle for 3+ days.
![Dreame Automations](../www/custom_ui/floorplan/images/branding/Dreame%20Automations.png) ![Dreame Automations](../www/custom_ui/floorplan/images/branding/Dreame%20Automations.png)
### Blog & video deep dives ### Blog & video deep dives

View File

@@ -3,8 +3,8 @@
# For more info visit https://www.vcloudinfo.com/click-here # For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Dreame Vacuum Orchestration - Room queue, away/on-demand runs # Dreame Vacuum Orchestration - Continuous phased sweep/mop with away/on-demand
# Weekday sweep, weekend mop, bathrooms last, notifications # Phases: sweep main, sweep baths, mop main, mop baths; notifications + idle auto-start
# ------------------------------------------------------------------- # -------------------------------------------------------------------
###################################################################### ######################################################################
@@ -12,29 +12,31 @@
###################################################################### ######################################################################
input_boolean: input_boolean:
l10s_vacuum_weekday_cycle_active:
name: L10s Weekday Cleaning Active
icon: mdi:robot-vacuum
l10s_vacuum_on_demand: l10s_vacuum_on_demand:
name: Dream Clean (On-Demand) name: Dreame Clean (On-Demand)
icon: mdi:rocket-launch icon: mdi:robot-vacuum
input_datetime: input_select:
l10s_vacuum_last_weekday_cycle: l10s_vacuum_phase:
name: L10s Last Weekday Cleaning Cycle name: L10s Vacuum Phase
has_date: true options:
has_time: true - sweep_main
- sweep_bath
- mop_main
- mop_bath
initial: sweep_main
icon: mdi:playlist-check
input_text: input_text:
l10s_vacuum_room_queue: l10s_vacuum_room_queue:
name: L10s Vacuum Room Queue name: L10s Vacuum Room Queue
# Room order (id:name): 14 Kitchen, 12 Dining, 10 Living, 7 Master Bedroom, 15 Foyer, 9 Stacey Office,
# 17 Formal Dining, 13 Hallway, 8 Justin Bedroom, 6 Paige Bedroom, 4 Master Bathroom, 2 Office, 1 Pool Bath, 3 Kids Bathroom.
icon: mdi:format-list-bulleted icon: mdi:format-list-bulleted
max: 255 max: 255
l10s_vacuum_room_catalog: l10s_vacuum_room_catalog:
name: L10s Vacuum Room Catalog name: L10s Vacuum Room Catalog
# Room order (id:name): 14 Kitchen, 12 Dining, 10 Living, 7 Master Bedroom, 15 Foyer, 9 Stacey Office, initial: "6,7,8,9,10,12,13,14,15,17,2,4,1,3"
# 17 Formal Dining, 13 Hallway, 8 Justin Bedroom, 6 Paige Bedroom, 4 Master Bathroom, 2 Office, 1 Pool Bath, 3 Kids Bathroom.
initial: "14,12,10,7,15,9,17,13,8,6,4,2,1,3"
icon: mdi:map icon: mdi:map
max: 255 max: 255
l10s_vacuum_rooms_cleaned_today: l10s_vacuum_rooms_cleaned_today:
@@ -52,54 +54,39 @@ script:
mode: single mode: single
sequence: sequence:
- variables: - variables:
# Weekday runs are sweeping (vacuum only), weekend runs are mopping
cleaning_mode: "{{ 'mopping' if now().weekday() in [5, 6] else 'sweeping' }}"
catalog_raw: "{{ states('input_text.l10s_vacuum_room_catalog') | default('', true) | string | replace(' ', '') }}" catalog_raw: "{{ states('input_text.l10s_vacuum_room_catalog') | default('', true) | string | replace(' ', '') }}"
queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
last_reset_raw: "{{ states('input_datetime.l10s_vacuum_last_weekday_cycle') }}"
last_reset_date_str: >
{% set dt = as_datetime(last_reset_raw, default=None) %}
{{ dt.date().isoformat() if dt is not none else '' }}
catalog_ints: "{{ catalog_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}" catalog_ints: "{{ catalog_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
# Seed if queue is empty AND last reset was not today
can_seed_today: "{{ last_reset_date_str == '' or last_reset_date_str != now().date().isoformat() }}"
will_seed: >
{% set empty_queue = queue_ints | length == 0 %}
{% set on_demand = is_state('input_boolean.l10s_vacuum_on_demand', 'on') %}
{{ (empty_queue or (on_demand and queue_ints | length <= 1)) and catalog_ints | length > 0 and (can_seed_today or on_demand) }}
seeded_queue_list: "{{ catalog_ints if will_seed else queue_ints }}"
valid_queue_list: "{{ seeded_queue_list }}"
# Define bathroom IDs for mopping separation
bath_ids: [1, 3, 4] bath_ids: [1, 3, 4]
nonbath_list: "{{ valid_queue_list | reject('in', bath_ids) | list }}" main_ids: "{{ catalog_ints | reject('in', bath_ids) | list }}"
bath_list: "{{ valid_queue_list | select('in', bath_ids) | list }}" phase_order: ['sweep_main', 'sweep_bath', 'mop_main', 'mop_bath']
# Prioritize non-bathrooms first, then bathrooms phase_state: "{{ states('input_select.l10s_vacuum_phase') }}"
segments_to_clean: > phase: "{{ phase_state if phase_state in phase_order else 'sweep_main' }}"
{% if nonbath_list | length > 0 %} cleaning_mode: "{{ 'mopping' if 'mop_' in phase else 'sweeping' }}"
{{ nonbath_list }} queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
{% elif bath_list | length > 0 %} queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
{{ bath_list }} phase_segments: >
{% if phase == 'sweep_main' %}
{{ main_ids }}
{% elif phase == 'sweep_bath' %}
{{ bath_ids }}
{% elif phase == 'mop_main' %}
{{ main_ids }}
{% else %} {% else %}
[] {{ bath_ids }}
{% endif %} {% endif %}
segments_to_clean: "{{ queue_ints if queue_ints | length > 0 else phase_segments }}"
# 1. Seed the queue if necessary # 1. Seed the queue if necessary
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ will_seed }}" value_template: "{{ queue_ints | length == 0 and phase_segments | length > 0 }}"
sequence: sequence:
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: input_text.l10s_vacuum_room_queue entity_id: input_text.l10s_vacuum_room_queue
data: data:
value: "{{ catalog_raw }}" value: "{{ phase_segments | join(',') }}"
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
default: [] default: []
# 2. Check if there is anything to clean and stop if not # 2. Check if there is anything to clean and stop if not
@@ -122,9 +109,6 @@ script:
entity_id: vacuum.l10s_vacuum entity_id: vacuum.l10s_vacuum
data: data:
fan_speed: Standard fan_speed: Standard
- service: input_boolean.turn_on
target:
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
- service: dreame_vacuum.vacuum_clean_segment - service: dreame_vacuum.vacuum_clean_segment
target: target:
entity_id: vacuum.l10s_vacuum entity_id: vacuum.l10s_vacuum
@@ -138,36 +122,30 @@ script:
automation: automation:
- alias: 'Away Vacuum: Reset Queue (Mon/Sat)' - alias: 'Vacuum: Reset Cleaned List at 5am'
id: 93a6e7dc-9c32-4d53-9f7c-651cd60f4b84 id: 18f7b6d3-c02c-4ec1-88b3-0c3b8b4c6f7b
trigger: trigger:
- platform: time - platform: time
at: '08:55:00' at: '05:00:00'
condition:
- condition: time
weekday:
- mon
- sat
action: action:
- service: input_text.set_value
target:
entity_id: input_text.l10s_vacuum_room_queue
data:
value: "{{ states('input_text.l10s_vacuum_room_catalog') }}"
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: input_text.l10s_vacuum_rooms_cleaned_today entity_id: input_text.l10s_vacuum_rooms_cleaned_today
data: data:
value: "" value: ""
- service: input_boolean.turn_off
target: - alias: 'Vacuum: Auto-Start if Idle 3 Days'
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active id: c6b3f1e8-9a3f-4098-9b9e-1c7f2d6f1d11
- service: input_datetime.set_datetime trigger:
target: - platform: time
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle at: '16:00:00'
data: condition:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" - condition: template
- service: input_boolean.turn_off value_template: >
{% set last = state_attr('script.l10s_vacuum_start_next_room','last_triggered') %}
{{ last is none or (now() - last).days >= 3 }}
action:
- service: input_boolean.turn_on
target: target:
entity_id: input_boolean.l10s_vacuum_on_demand entity_id: input_boolean.l10s_vacuum_on_demand
@@ -202,9 +180,6 @@ automation:
entity_id: input_boolean.l10s_vacuum_on_demand entity_id: input_boolean.l10s_vacuum_on_demand
to: 'off' to: 'off'
condition: condition:
- condition: state
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
state: 'on'
- condition: template - condition: template
value_template: > value_template: >
{{ is_state('vacuum.l10s_vacuum', 'cleaning') or is_state('vacuum.l10s_vacuum', 'returning') or is_state('vacuum.l10s_vacuum', 'paused') }} {{ is_state('vacuum.l10s_vacuum', 'cleaning') or is_state('vacuum.l10s_vacuum', 'returning') or is_state('vacuum.l10s_vacuum', 'paused') }}
@@ -223,52 +198,47 @@ automation:
- platform: state - platform: state
entity_id: sensor.l10s_vacuum_current_room entity_id: sensor.l10s_vacuum_current_room
for: '00:03:00' for: '00:03:00'
- platform: state
entity_id: vacuum.l10s_vacuum
to: 'cleaning'
for: '00:03:00'
variables: variables:
room_map: {14:'kitchen',12:'dining-room',10:'living room',7:'master-bedroom',15:'foyer',9:'stacey-office',17:'formal-dining',13:'hallway',8:'justin-bedroom',6:'paige-bedroom',4:'master-bathroom',2:'office',1:'pool-bath',3:'kids-bathroom'} room_map: {14:'kitchen',12:'dining-room',10:'living room',7:'master-bedroom',15:'foyer',9:'stacey-office',17:'formal-dining',13:'hallway',8:'justin-bedroom',6:'paige-bedroom',4:'master-bathroom',2:'office',1:'pool-bath',3:'kids-bathroom'}
catalog_raw: "{{ states('input_text.l10s_vacuum_room_catalog') | default('', true) | string | replace(' ', '') }}"
catalog_ints: "{{ catalog_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}" queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list | default([], true) }}" queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | list | default([], true) }}"
working_queue: "{{ queue_ints if queue_ints | length > 0 else catalog_ints }}"
current_room_id: "{{ trigger.to_state.attributes.room_id | default(state_attr('sensor.l10s_vacuum_current_room', 'room_id'), true) | int(0) }}" current_room_id: "{{ trigger.to_state.attributes.room_id | default(state_attr('sensor.l10s_vacuum_current_room', 'room_id'), true) | int(0) }}"
matched_room_id: "{{ current_room_id if current_room_id > 0 and current_room_id in (working_queue | default([], true)) else 0 }}" matched_room_id: "{{ current_room_id if current_room_id > 0 and current_room_id in (queue_ints | default([], true)) else 0 }}"
remaining_rooms: "{{ working_queue | reject('equalto', matched_room_id) | list | join(',') }}" remaining_list: >
remaining_value: > {% set rem = [] %}
{% set rem = remaining_rooms | string %} {% set removed = namespace(done=false) %}
{% if rem | length == 0 and working_queue | length > 1 %} {% for r in queue_ints %}
{{ working_queue | join(',') }} {% if not removed.done and r == matched_room_id %}
{% set removed.done = true %}
{% else %} {% else %}
{% set rem = rem + [r] %}
{% endif %}
{% endfor %}
{{ rem }} {{ rem }}
{% endif %} remaining_rooms: "{{ remaining_list | join(',') }}"
remaining_value_str: > remaining_count: "{{ remaining_list | length }}"
{% set rv = remaining_value %} phase_order: ['sweep_main', 'sweep_bath', 'mop_main', 'mop_bath']
{% if rv is string %} phase_state: "{{ states('input_select.l10s_vacuum_phase') }}"
{{ rv }} phase: "{{ phase_state if phase_state in phase_order else 'sweep_main' }}"
{% elif rv is iterable %} phase_index: "{{ phase_order.index(phase) if phase in phase_order else 0 }}"
{{ rv | map('string') | join(',') }} has_next_phase: "{{ phase_index < (phase_order | length) - 1 }}"
{% else %} next_phase: "{{ phase_order[phase_index + 1] if has_next_phase else '' }}"
{{ rv | string }}
{% endif %}
condition: condition:
# Only run if there's actually a queue and a room was successfully matched to the start of the queue
- condition: template - condition: template
value_template: "{{ working_queue | length > 0 }}" value_template: "{{ queue_ints | length > 0 }}"
- condition: template
value_template: "{{ matched_room_id != 0 }}"
- condition: template - condition: template
value_template: "{{ matched_room_id != 0 }}" value_template: "{{ matched_room_id != 0 }}"
- condition: state
entity_id: vacuum.l10s_vacuum
state: 'cleaning'
action: action:
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: input_text.l10s_vacuum_room_queue entity_id: input_text.l10s_vacuum_room_queue
data: data:
value: "{{ remaining_value_str }}" value: "{{ remaining_rooms }}"
- variables: - variables:
cleaned_raw: "{{ states('input_text.l10s_vacuum_rooms_cleaned_today') | default('', true) | string }}" cleaned_raw: "{{ states('input_text.l10s_vacuum_rooms_cleaned_today') | default('', true) | string }}"
cleaned_parts: "{{ cleaned_raw | regex_findall('[^,]+') | map('trim') | reject('equalto','') | list }}" cleaned_parts: "{{ cleaned_raw | regex_findall('[^,]+') | map('trim') | reject('equalto','') | list }}"
@@ -280,7 +250,6 @@ automation:
{% else %} {% else %}
{{ parts | join(', ') }} {{ parts | join(', ') }}
{% endif %} {% endif %}
remaining_count: "{{ remaining_value_str | regex_findall('[^,]+') | length if remaining_value_str | length > 0 else 0 }}"
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: input_text.l10s_vacuum_rooms_cleaned_today entity_id: input_text.l10s_vacuum_rooms_cleaned_today
@@ -299,46 +268,39 @@ automation:
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ remaining_rooms | length > 0 }}" value_template: "{{ remaining_count > 0 }}"
sequence: sequence:
- service: script.l10s_vacuum_start_next_room - service: script.l10s_vacuum_start_next_room
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ remaining_rooms | length == 0 }}" value_template: "{{ remaining_count == 0 and has_next_phase }}"
sequence: sequence:
- service: input_boolean.turn_off - service: input_select.select_option
target: target:
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active entity_id: input_select.l10s_vacuum_phase
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
data: data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" option: "{{ next_phase }}"
- service: input_text.set_value
- alias: 'Away Vacuum: Cycle Complete'
id: 8fa7779a-957b-49a3-84e7-36ca93c2e0d2
trigger:
- platform: state
entity_id: sensor.l10s_vacuum_task_status
to: 'completed'
- platform: state
entity_id: vacuum.l10s_vacuum
to: 'docked'
for: 00:05:00
condition:
- condition: template
value_template: "{{ is_state('sensor.l10s_vacuum_task_status', 'completed') }}"
- condition: template
value_template: "{{ (states('input_text.l10s_vacuum_room_queue') | replace(' ', '')) | length == 0 }}"
action:
- service: input_boolean.turn_off
target: target:
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active entity_id: input_text.l10s_vacuum_room_queue
- service: input_datetime.set_datetime
target:
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
data: data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}" value: ""
- service: script.l10s_vacuum_start_next_room
- conditions:
- condition: template
value_template: "{{ remaining_count == 0 and not has_next_phase }}"
sequence:
- service: input_select.select_option
target:
entity_id: input_select.l10s_vacuum_phase
data:
option: "sweep_main"
- service: input_text.set_value
target:
entity_id: input_text.l10s_vacuum_room_queue
data:
value: ""
- service: script.l10s_vacuum_start_next_room
- alias: 'Vacuum Sensor Cleaning Silencer' - alias: 'Vacuum Sensor Cleaning Silencer'
id: 6548de52-a4a4-4df2-9d66-9c2c15577a7f id: 6548de52-a4a4-4df2-9d66-9c2c15577a7f