Compare commits

..

2 Commits

Author SHA1 Message Date
github-actions[bot]
2265f2b282 Update HA version badge to 2025.12.1 2025-12-09 18:14:27 +00:00
Carlo Costanzo
ea8d57393a 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.
2025-12-09 13:14:07 -05:00
3 changed files with 110 additions and 148 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

View File

@@ -1,13 +1,13 @@
<svg width="68.5" height="20" viewBox="0 0 685 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="2025.12.1"> <svg width="68.5" height="20" viewBox="0 0 685 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="2025.12.1">
<title>2025.12.1</title> <title>2025.12.1</title>
<linearGradient id="GGkaK" x2="0" y2="100%"> <linearGradient id="zseUi" x2="0" y2="100%">
<stop offset="0" stop-opacity=".1" stop-color="#EEE"/> <stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
<stop offset="1" stop-opacity=".1"/> <stop offset="1" stop-opacity=".1"/>
</linearGradient> </linearGradient>
<mask id="wwoCE"><rect width="685" height="200" rx="30" fill="#FFF"/></mask> <mask id="nBbtk"><rect width="685" height="200" rx="30" fill="#FFF"/></mask>
<g mask="url(#wwoCE)"> <g mask="url(#nBbtk)">
<rect width="685" height="200" fill="#08C" x="0"/> <rect width="685" height="200" fill="#08C" x="0"/>
<rect width="685" height="200" fill="url(#GGkaK)"/> <rect width="685" height="200" fill="url(#zseUi)"/>
</g> </g>
<g aria-hidden="true" fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110"> <g aria-hidden="true" fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110">
<text x="65" y="148" textLength="570" fill="#000" opacity="0.25">2025.12.1</text> <text x="65" y="148" textLength="570" fill="#000" opacity="0.25">2025.12.1</text>

Before

Width:  |  Height:  |  Size: 815 B

After

Width:  |  Height:  |  Size: 815 B