Skip to content

Commit ea8d573

Browse files
committed
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.
1 parent ddda9e6 commit ea8d573

File tree

2 files changed

+105
-143
lines changed

2 files changed

+105
-143
lines changed

config/packages/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
4545
| [lightning.yaml](lightning.yaml) | Blitzortung lightning counter monitoring with snoozeable push actions. | `sensor.blitzortung_lightning_counter`, `input_boolean.snooze_lightning`, notify engine actions |
4646
| [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` |
4747
| [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` |
48-
| [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` |
48+
| [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` |
4949
| [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` |
5050
| [finance.yaml](finance.yaml) | Yahoo Finance sensor bundle for portfolio glances and Lovelace cards. | `sensor.tsla`, `sensor.aapl`, `sensor.amzn`, `sensor.msft` |
5151

@@ -60,8 +60,8 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
6060
![Nest Climate Control](../www/custom_ui/floorplan/images/branding/Nest_Climate_Control.png)
6161

6262
### Dreame vacuum automations
63-
- 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.
64-
- Uses the Dreame HACS integration with map-based segments and daily resets to cover the whole house without repeating rooms.
63+
- 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.
64+
- 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.
6565
![Dreame Automations](../www/custom_ui/floorplan/images/branding/Dreame%20Automations.png)
6666

6767
### Blog & video deep dives

config/packages/vacuum.yaml

Lines changed: 102 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,40 @@
33
# For more info visit https://www.vcloudinfo.com/click-here
44
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
55
# -------------------------------------------------------------------
6-
# Dreame Vacuum Orchestration - Room queue, away/on-demand runs
7-
# Weekday sweep, weekend mop, bathrooms last, notifications
6+
# Dreame Vacuum Orchestration - Continuous phased sweep/mop with away/on-demand
7+
# Phases: sweep main, sweep baths, mop main, mop baths; notifications + idle auto-start
88
# -------------------------------------------------------------------
99
######################################################################
1010

1111
## 1. Helpers
1212
######################################################################
1313

1414
input_boolean:
15-
l10s_vacuum_weekday_cycle_active:
16-
name: L10s Weekday Cleaning Active
17-
icon: mdi:robot-vacuum
1815
l10s_vacuum_on_demand:
19-
name: Dream Clean (On-Demand)
20-
icon: mdi:rocket-launch
16+
name: Dreame Clean (On-Demand)
17+
icon: mdi:robot-vacuum
2118

22-
input_datetime:
23-
l10s_vacuum_last_weekday_cycle:
24-
name: L10s Last Weekday Cleaning Cycle
25-
has_date: true
26-
has_time: true
19+
input_select:
20+
l10s_vacuum_phase:
21+
name: L10s Vacuum Phase
22+
options:
23+
- sweep_main
24+
- sweep_bath
25+
- mop_main
26+
- mop_bath
27+
initial: sweep_main
28+
icon: mdi:playlist-check
2729

2830
input_text:
2931
l10s_vacuum_room_queue:
3032
name: L10s Vacuum Room Queue
33+
# Room order (id:name): 14 Kitchen, 12 Dining, 10 Living, 7 Master Bedroom, 15 Foyer, 9 Stacey Office,
34+
# 17 Formal Dining, 13 Hallway, 8 Justin Bedroom, 6 Paige Bedroom, 4 Master Bathroom, 2 Office, 1 Pool Bath, 3 Kids Bathroom.
3135
icon: mdi:format-list-bulleted
3236
max: 255
3337
l10s_vacuum_room_catalog:
3438
name: L10s Vacuum Room Catalog
35-
# Room order (id:name): 14 Kitchen, 12 Dining, 10 Living, 7 Master Bedroom, 15 Foyer, 9 Stacey Office,
36-
# 17 Formal Dining, 13 Hallway, 8 Justin Bedroom, 6 Paige Bedroom, 4 Master Bathroom, 2 Office, 1 Pool Bath, 3 Kids Bathroom.
37-
initial: "14,12,10,7,15,9,17,13,8,6,4,2,1,3"
39+
initial: "6,7,8,9,10,12,13,14,15,17,2,4,1,3"
3840
icon: mdi:map
3941
max: 255
4042
l10s_vacuum_rooms_cleaned_today:
@@ -52,54 +54,39 @@ script:
5254
mode: single
5355
sequence:
5456
- variables:
55-
# Weekday runs are sweeping (vacuum only), weekend runs are mopping
56-
cleaning_mode: "{{ 'mopping' if now().weekday() in [5, 6] else 'sweeping' }}"
5757
catalog_raw: "{{ states('input_text.l10s_vacuum_room_catalog') | default('', true) | string | replace(' ', '') }}"
58-
queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
59-
last_reset_raw: "{{ states('input_datetime.l10s_vacuum_last_weekday_cycle') }}"
60-
last_reset_date_str: >
61-
{% set dt = as_datetime(last_reset_raw, default=None) %}
62-
{{ dt.date().isoformat() if dt is not none else '' }}
6358
catalog_ints: "{{ catalog_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
64-
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
65-
# Seed if queue is empty AND last reset was not today
66-
can_seed_today: "{{ last_reset_date_str == '' or last_reset_date_str != now().date().isoformat() }}"
67-
will_seed: >
68-
{% set empty_queue = queue_ints | length == 0 %}
69-
{% set on_demand = is_state('input_boolean.l10s_vacuum_on_demand', 'on') %}
70-
{{ (empty_queue or (on_demand and queue_ints | length <= 1)) and catalog_ints | length > 0 and (can_seed_today or on_demand) }}
71-
seeded_queue_list: "{{ catalog_ints if will_seed else queue_ints }}"
72-
valid_queue_list: "{{ seeded_queue_list }}"
73-
# Define bathroom IDs for mopping separation
7459
bath_ids: [1, 3, 4]
75-
nonbath_list: "{{ valid_queue_list | reject('in', bath_ids) | list }}"
76-
bath_list: "{{ valid_queue_list | select('in', bath_ids) | list }}"
77-
# Prioritize non-bathrooms first, then bathrooms
78-
segments_to_clean: >
79-
{% if nonbath_list | length > 0 %}
80-
{{ nonbath_list }}
81-
{% elif bath_list | length > 0 %}
82-
{{ bath_list }}
60+
main_ids: "{{ catalog_ints | reject('in', bath_ids) | list }}"
61+
phase_order: ['sweep_main', 'sweep_bath', 'mop_main', 'mop_bath']
62+
phase_state: "{{ states('input_select.l10s_vacuum_phase') }}"
63+
phase: "{{ phase_state if phase_state in phase_order else 'sweep_main' }}"
64+
cleaning_mode: "{{ 'mopping' if 'mop_' in phase else 'sweeping' }}"
65+
queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
66+
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
67+
phase_segments: >
68+
{% if phase == 'sweep_main' %}
69+
{{ main_ids }}
70+
{% elif phase == 'sweep_bath' %}
71+
{{ bath_ids }}
72+
{% elif phase == 'mop_main' %}
73+
{{ main_ids }}
8374
{% else %}
84-
[]
75+
{{ bath_ids }}
8576
{% endif %}
77+
segments_to_clean: "{{ queue_ints if queue_ints | length > 0 else phase_segments }}"
8678

8779
# 1. Seed the queue if necessary
8880
- choose:
8981
- conditions:
9082
- condition: template
91-
value_template: "{{ will_seed }}"
83+
value_template: "{{ queue_ints | length == 0 and phase_segments | length > 0 }}"
9284
sequence:
9385
- service: input_text.set_value
9486
target:
9587
entity_id: input_text.l10s_vacuum_room_queue
9688
data:
97-
value: "{{ catalog_raw }}"
98-
- service: input_datetime.set_datetime
99-
target:
100-
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
101-
data:
102-
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
89+
value: "{{ phase_segments | join(',') }}"
10390
default: []
10491

10592
# 2. Check if there is anything to clean and stop if not
@@ -122,9 +109,6 @@ script:
122109
entity_id: vacuum.l10s_vacuum
123110
data:
124111
fan_speed: Standard
125-
- service: input_boolean.turn_on
126-
target:
127-
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
128112
- service: dreame_vacuum.vacuum_clean_segment
129113
target:
130114
entity_id: vacuum.l10s_vacuum
@@ -138,36 +122,30 @@ script:
138122

139123
automation:
140124

141-
- alias: 'Away Vacuum: Reset Queue (Mon/Sat)'
142-
id: 93a6e7dc-9c32-4d53-9f7c-651cd60f4b84
125+
- alias: 'Vacuum: Reset Cleaned List at 5am'
126+
id: 18f7b6d3-c02c-4ec1-88b3-0c3b8b4c6f7b
143127
trigger:
144128
- platform: time
145-
at: '08:55:00'
146-
condition:
147-
- condition: time
148-
weekday:
149-
- mon
150-
- sat
129+
at: '05:00:00'
151130
action:
152-
- service: input_text.set_value
153-
target:
154-
entity_id: input_text.l10s_vacuum_room_queue
155-
data:
156-
value: "{{ states('input_text.l10s_vacuum_room_catalog') }}"
157131
- service: input_text.set_value
158132
target:
159133
entity_id: input_text.l10s_vacuum_rooms_cleaned_today
160134
data:
161135
value: ""
162-
- service: input_boolean.turn_off
163-
target:
164-
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
165-
- service: input_datetime.set_datetime
166-
target:
167-
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
168-
data:
169-
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
170-
- service: input_boolean.turn_off
136+
137+
- alias: 'Vacuum: Auto-Start if Idle 3 Days'
138+
id: c6b3f1e8-9a3f-4098-9b9e-1c7f2d6f1d11
139+
trigger:
140+
- platform: time
141+
at: '16:00:00'
142+
condition:
143+
- condition: template
144+
value_template: >
145+
{% set last = state_attr('script.l10s_vacuum_start_next_room','last_triggered') %}
146+
{{ last is none or (now() - last).days >= 3 }}
147+
action:
148+
- service: input_boolean.turn_on
171149
target:
172150
entity_id: input_boolean.l10s_vacuum_on_demand
173151

@@ -202,9 +180,6 @@ automation:
202180
entity_id: input_boolean.l10s_vacuum_on_demand
203181
to: 'off'
204182
condition:
205-
- condition: state
206-
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
207-
state: 'on'
208183
- condition: template
209184
value_template: >
210185
{{ 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:
223198
- platform: state
224199
entity_id: sensor.l10s_vacuum_current_room
225200
for: '00:03:00'
226-
- platform: state
227-
entity_id: vacuum.l10s_vacuum
228-
to: 'cleaning'
229-
for: '00:03:00'
230201
variables:
231202
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'}
232-
catalog_raw: "{{ states('input_text.l10s_vacuum_room_catalog') | default('', true) | string | replace(' ', '') }}"
233-
catalog_ints: "{{ catalog_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list }}"
234203
queue_raw: "{{ states('input_text.l10s_vacuum_room_queue') | default('', true) | string | replace(' ', '') }}"
235-
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | select('gt', 0) | list | default([], true) }}"
236-
working_queue: "{{ queue_ints if queue_ints | length > 0 else catalog_ints }}"
204+
queue_ints: "{{ queue_raw | regex_findall('[0-9]+') | map('int') | list | default([], true) }}"
237205
current_room_id: "{{ trigger.to_state.attributes.room_id | default(state_attr('sensor.l10s_vacuum_current_room', 'room_id'), true) | int(0) }}"
238-
matched_room_id: "{{ current_room_id if current_room_id > 0 and current_room_id in (working_queue | default([], true)) else 0 }}"
239-
remaining_rooms: "{{ working_queue | reject('equalto', matched_room_id) | list | join(',') }}"
240-
remaining_value: >
241-
{% set rem = remaining_rooms | string %}
242-
{% if rem | length == 0 and working_queue | length > 1 %}
243-
{{ working_queue | join(',') }}
244-
{% else %}
245-
{{ rem }}
246-
{% endif %}
247-
remaining_value_str: >
248-
{% set rv = remaining_value %}
249-
{% if rv is string %}
250-
{{ rv }}
251-
{% elif rv is iterable %}
252-
{{ rv | map('string') | join(',') }}
253-
{% else %}
254-
{{ rv | string }}
255-
{% endif %}
206+
matched_room_id: "{{ current_room_id if current_room_id > 0 and current_room_id in (queue_ints | default([], true)) else 0 }}"
207+
remaining_list: >
208+
{% set rem = [] %}
209+
{% set removed = namespace(done=false) %}
210+
{% for r in queue_ints %}
211+
{% if not removed.done and r == matched_room_id %}
212+
{% set removed.done = true %}
213+
{% else %}
214+
{% set rem = rem + [r] %}
215+
{% endif %}
216+
{% endfor %}
217+
{{ rem }}
218+
remaining_rooms: "{{ remaining_list | join(',') }}"
219+
remaining_count: "{{ remaining_list | length }}"
220+
phase_order: ['sweep_main', 'sweep_bath', 'mop_main', 'mop_bath']
221+
phase_state: "{{ states('input_select.l10s_vacuum_phase') }}"
222+
phase: "{{ phase_state if phase_state in phase_order else 'sweep_main' }}"
223+
phase_index: "{{ phase_order.index(phase) if phase in phase_order else 0 }}"
224+
has_next_phase: "{{ phase_index < (phase_order | length) - 1 }}"
225+
next_phase: "{{ phase_order[phase_index + 1] if has_next_phase else '' }}"
256226

257227
condition:
258-
# Only run if there's actually a queue and a room was successfully matched to the start of the queue
259-
- condition: template
260-
value_template: "{{ working_queue | length > 0 }}"
261228
- condition: template
262-
value_template: "{{ matched_room_id != 0 }}"
229+
value_template: "{{ queue_ints | length > 0 }}"
263230
- condition: template
264231
value_template: "{{ matched_room_id != 0 }}"
232+
- condition: state
233+
entity_id: vacuum.l10s_vacuum
234+
state: 'cleaning'
265235

266236
action:
267237
- service: input_text.set_value
268238
target:
269239
entity_id: input_text.l10s_vacuum_room_queue
270240
data:
271-
value: "{{ remaining_value_str }}"
241+
value: "{{ remaining_rooms }}"
272242
- variables:
273243
cleaned_raw: "{{ states('input_text.l10s_vacuum_rooms_cleaned_today') | default('', true) | string }}"
274244
cleaned_parts: "{{ cleaned_raw | regex_findall('[^,]+') | map('trim') | reject('equalto','') | list }}"
@@ -280,7 +250,6 @@ automation:
280250
{% else %}
281251
{{ parts | join(', ') }}
282252
{% endif %}
283-
remaining_count: "{{ remaining_value_str | regex_findall('[^,]+') | length if remaining_value_str | length > 0 else 0 }}"
284253
- service: input_text.set_value
285254
target:
286255
entity_id: input_text.l10s_vacuum_rooms_cleaned_today
@@ -299,46 +268,39 @@ automation:
299268
- choose:
300269
- conditions:
301270
- condition: template
302-
value_template: "{{ remaining_rooms | length > 0 }}"
271+
value_template: "{{ remaining_count > 0 }}"
303272
sequence:
304273
- service: script.l10s_vacuum_start_next_room
305274
- conditions:
306275
- condition: template
307-
value_template: "{{ remaining_rooms | length == 0 }}"
276+
value_template: "{{ remaining_count == 0 and has_next_phase }}"
308277
sequence:
309-
- service: input_boolean.turn_off
278+
- service: input_select.select_option
310279
target:
311-
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
312-
- service: input_datetime.set_datetime
280+
entity_id: input_select.l10s_vacuum_phase
281+
data:
282+
option: "{{ next_phase }}"
283+
- service: input_text.set_value
313284
target:
314-
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
285+
entity_id: input_text.l10s_vacuum_room_queue
315286
data:
316-
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
317-
318-
- alias: 'Away Vacuum: Cycle Complete'
319-
id: 8fa7779a-957b-49a3-84e7-36ca93c2e0d2
320-
trigger:
321-
- platform: state
322-
entity_id: sensor.l10s_vacuum_task_status
323-
to: 'completed'
324-
- platform: state
325-
entity_id: vacuum.l10s_vacuum
326-
to: 'docked'
327-
for: 00:05:00
328-
condition:
329-
- condition: template
330-
value_template: "{{ is_state('sensor.l10s_vacuum_task_status', 'completed') }}"
331-
- condition: template
332-
value_template: "{{ (states('input_text.l10s_vacuum_room_queue') | replace(' ', '')) | length == 0 }}"
333-
action:
334-
- service: input_boolean.turn_off
335-
target:
336-
entity_id: input_boolean.l10s_vacuum_weekday_cycle_active
337-
- service: input_datetime.set_datetime
338-
target:
339-
entity_id: input_datetime.l10s_vacuum_last_weekday_cycle
340-
data:
341-
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
287+
value: ""
288+
- service: script.l10s_vacuum_start_next_room
289+
- conditions:
290+
- condition: template
291+
value_template: "{{ remaining_count == 0 and not has_next_phase }}"
292+
sequence:
293+
- service: input_select.select_option
294+
target:
295+
entity_id: input_select.l10s_vacuum_phase
296+
data:
297+
option: "sweep_main"
298+
- service: input_text.set_value
299+
target:
300+
entity_id: input_text.l10s_vacuum_room_queue
301+
data:
302+
value: ""
303+
- service: script.l10s_vacuum_start_next_room
342304

343305
- alias: 'Vacuum Sensor Cleaning Silencer'
344306
id: 6548de52-a4a4-4df2-9d66-9c2c15577a7f

0 commit comments

Comments
 (0)