Compare commits
4 Commits
6848c3f903
...
e98df855d9
| Author | SHA1 | Date | |
|---|---|---|---|
| e98df855d9 | |||
| 29b1044d63 | |||
| 006f9e532c | |||
| 9383f957b6 |
94
Common/Dreame Vacuum/README.md
Normal file
94
Common/Dreame Vacuum/README.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Dreame Vacuum Notifications
|
||||||
|
|
||||||
|
Sends customizable notifications for Dreame vacuum events. Requires the [Dreame Vacuum](https://github.com/Tasshack/dreame-vacuum) integration.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Notifications for cleaning started and completed
|
||||||
|
- 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)
|
||||||
|
- Individual toggle for each event type
|
||||||
|
- Customizable message templates with variable substitution
|
||||||
|
- Multiple notification targets
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The blueprint listens to events fired by the Dreame Vacuum integration:
|
||||||
|
|
||||||
|
| Event | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `dreame_vacuum_task_status` | Cleaning job started or completed |
|
||||||
|
| `dreame_vacuum_consumable` | Consumable reached end-of-life |
|
||||||
|
| `dreame_vacuum_warning` | Dismissible device warning |
|
||||||
|
| `dreame_vacuum_error` | Device fault |
|
||||||
|
| `dreame_vacuum_information` | Action blocked by user settings |
|
||||||
|
|
||||||
|
Events are filtered by the configured vacuum entity, so only the selected vacuum triggers notifications.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Input | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| **Vacuum Entity** | The Dreame vacuum entity to monitor |
|
||||||
|
| **Notification Targets** | One or more `notify` entities |
|
||||||
|
| **Event Toggles** | Enable/disable each event type independently |
|
||||||
|
| **Message Templates** | Customizable message for each event type |
|
||||||
|
|
||||||
|
## Message Template Variables
|
||||||
|
|
||||||
|
### Cleaning Started
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{cleaning_mode}` | Cleaning mode (e.g., sweeping, mopping) |
|
||||||
|
| `{status}` | Current status |
|
||||||
|
|
||||||
|
### Cleaning Completed
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{cleaning_mode}` | Cleaning mode used |
|
||||||
|
| `{status}` | Final status |
|
||||||
|
| `{cleaned_area}` | Area cleaned (m²) |
|
||||||
|
| `{cleaning_time}` | Cleaning duration (minutes) |
|
||||||
|
|
||||||
|
### Consumable Depleted
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{consumable}` | Consumable name (e.g., Main Brush, Side Brush, Filter, Mop Pad) |
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{warning}` | Warning description |
|
||||||
|
| `{code}` | Warning code |
|
||||||
|
|
||||||
|
### Error
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{error}` | Error description |
|
||||||
|
| `{code}` | Error code |
|
||||||
|
|
||||||
|
### Information
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `{vacuum_name}` | Friendly name of the vacuum |
|
||||||
|
| `{information}` | Information message (e.g., Dust Collection, Cleaning Paused) |
|
||||||
|
|
||||||
|
## Debug Mode
|
||||||
|
|
||||||
|
Enable **Debug Notifications** in the Debug section to send persistent notifications with raw event data for troubleshooting.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Alexei Dolgolyov (<dolgolyov.alexei@gmail.com>)
|
||||||
419
Common/Dreame Vacuum/blueprint.yaml
Normal file
419
Common/Dreame Vacuum/blueprint.yaml
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
# Dreame Vacuum Notifications
|
||||||
|
# Sends notifications for Dreame vacuum events (cleaning, errors, consumables).
|
||||||
|
# See README.md for detailed documentation.
|
||||||
|
#
|
||||||
|
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
|
||||||
|
|
||||||
|
blueprint:
|
||||||
|
name: "Custom: Dreame Vacuum Notifications"
|
||||||
|
description: >
|
||||||
|
Sends customizable notifications for Dreame vacuum events including
|
||||||
|
cleaning status, consumable alerts, warnings, and errors.
|
||||||
|
Requires the Dreame Vacuum integration (github.com/Tasshack/dreame-vacuum).
|
||||||
|
domain: automation
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# INPUT CONFIGURATION
|
||||||
|
# ===========================================================================
|
||||||
|
input:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Vacuum Configuration
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
vacuum_group:
|
||||||
|
name: "Vacuum"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
vacuum_entity:
|
||||||
|
name: Vacuum Entity
|
||||||
|
description: "The Dreame vacuum entity to monitor."
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
integration: dreame_vacuum
|
||||||
|
domain: vacuum
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Notification Configuration
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
notification_group:
|
||||||
|
name: "Notification"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
notify_targets:
|
||||||
|
name: Notification Targets
|
||||||
|
description: "Notification service entities to send messages to (select one or more)."
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: notify
|
||||||
|
multiple: true
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Event Toggles
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
toggles_group:
|
||||||
|
name: "Event Toggles"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
enable_cleaning_started:
|
||||||
|
name: Cleaning Started
|
||||||
|
description: "Send notification when the vacuum starts cleaning."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
enable_cleaning_completed:
|
||||||
|
name: Cleaning Completed
|
||||||
|
description: "Send notification when the vacuum finishes cleaning."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
enable_consumable:
|
||||||
|
name: Consumable Depleted
|
||||||
|
description: "Send notification when a consumable reaches end-of-life."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
enable_warning:
|
||||||
|
name: Warning
|
||||||
|
description: "Send notification for device warnings."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
enable_error:
|
||||||
|
name: Error
|
||||||
|
description: "Send notification for device errors."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
enable_information:
|
||||||
|
name: Information
|
||||||
|
description: "Send notification for informational alerts (e.g., blocked by DND)."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Message Templates
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
messages_group:
|
||||||
|
name: "Message Templates"
|
||||||
|
collapsed: true
|
||||||
|
input:
|
||||||
|
message_cleaning_started:
|
||||||
|
name: "Cleaning Started Message"
|
||||||
|
description: >
|
||||||
|
Message sent when the vacuum starts cleaning.
|
||||||
|
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`
|
||||||
|
default: "🧹 {vacuum_name} started cleaning ({cleaning_mode})."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
message_cleaning_completed:
|
||||||
|
name: "Cleaning Completed Message"
|
||||||
|
description: >
|
||||||
|
Message sent when the vacuum finishes cleaning.
|
||||||
|
Variables: `{vacuum_name}`, `{cleaning_mode}`, `{status}`,
|
||||||
|
`{cleaned_area}`, `{cleaning_time}`
|
||||||
|
default: "✅ {vacuum_name} finished cleaning. Area: {cleaned_area} m², time: {cleaning_time} min."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
message_consumable:
|
||||||
|
name: "Consumable Depleted Message"
|
||||||
|
description: >
|
||||||
|
Message sent when a consumable reaches end-of-life.
|
||||||
|
Variables: `{vacuum_name}`, `{consumable}`
|
||||||
|
default: "🔧 {vacuum_name}: {consumable} needs replacement."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
message_warning:
|
||||||
|
name: "Warning Message"
|
||||||
|
description: >
|
||||||
|
Message sent for device warnings.
|
||||||
|
Variables: `{vacuum_name}`, `{warning}`, `{code}`
|
||||||
|
default: "⚠️ {vacuum_name} warning: {warning} (code: {code})."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
message_error:
|
||||||
|
name: "Error Message"
|
||||||
|
description: >
|
||||||
|
Message sent for device errors.
|
||||||
|
Variables: `{vacuum_name}`, `{error}`, `{code}`
|
||||||
|
default: "❌ {vacuum_name} error: {error} (code: {code})."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
message_information:
|
||||||
|
name: "Information Message"
|
||||||
|
description: >
|
||||||
|
Message sent for informational alerts.
|
||||||
|
Variables: `{vacuum_name}`, `{information}`
|
||||||
|
default: "ℹ️ {vacuum_name}: {information}."
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Debug
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
debug:
|
||||||
|
name: "Debug"
|
||||||
|
collapsed: true
|
||||||
|
input:
|
||||||
|
enable_debug_notifications:
|
||||||
|
name: Enable Debug Notifications
|
||||||
|
description: >
|
||||||
|
Send persistent notifications for debugging automation behavior.
|
||||||
|
Shows raw event data and filtering decisions.
|
||||||
|
default: false
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
# Queued mode to avoid dropping rapid events
|
||||||
|
mode: queued
|
||||||
|
max: 5
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TRIGGERS
|
||||||
|
# =============================================================================
|
||||||
|
trigger:
|
||||||
|
# Cleaning job started or completed
|
||||||
|
- platform: event
|
||||||
|
event_type: dreame_vacuum_task_status
|
||||||
|
id: "task_status"
|
||||||
|
|
||||||
|
# Consumable reached end-of-life
|
||||||
|
- platform: event
|
||||||
|
event_type: dreame_vacuum_consumable
|
||||||
|
id: "consumable"
|
||||||
|
|
||||||
|
# Informational alert (job blocked by user settings)
|
||||||
|
- platform: event
|
||||||
|
event_type: dreame_vacuum_information
|
||||||
|
id: "information"
|
||||||
|
|
||||||
|
# Dismissible device warning
|
||||||
|
- platform: event
|
||||||
|
event_type: dreame_vacuum_warning
|
||||||
|
id: "warning"
|
||||||
|
|
||||||
|
# Device fault
|
||||||
|
- platform: event
|
||||||
|
event_type: dreame_vacuum_error
|
||||||
|
id: "error"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VARIABLES
|
||||||
|
# =============================================================================
|
||||||
|
variables:
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Input References
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
vacuum_entity: !input vacuum_entity
|
||||||
|
notify_targets: !input notify_targets
|
||||||
|
enable_debug_notifications: !input enable_debug_notifications
|
||||||
|
|
||||||
|
# Event toggles
|
||||||
|
enable_cleaning_started: !input enable_cleaning_started
|
||||||
|
enable_cleaning_completed: !input enable_cleaning_completed
|
||||||
|
enable_consumable: !input enable_consumable
|
||||||
|
enable_warning: !input enable_warning
|
||||||
|
enable_error: !input enable_error
|
||||||
|
enable_information: !input enable_information
|
||||||
|
|
||||||
|
# Message templates
|
||||||
|
message_cleaning_started_template: !input message_cleaning_started
|
||||||
|
message_cleaning_completed_template: !input message_cleaning_completed
|
||||||
|
message_consumable_template: !input message_consumable
|
||||||
|
message_warning_template: !input message_warning
|
||||||
|
message_error_template: !input message_error
|
||||||
|
message_information_template: !input message_information
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Vacuum Info
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
vacuum_name: "{{ state_attr(vacuum_entity, 'friendly_name') | default(vacuum_entity) }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Event Data (flat structure — fields are directly on trigger.event.data)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
event_entity_id: "{{ trigger.event.data.entity_id | default('') }}"
|
||||||
|
|
||||||
|
# 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) }}"
|
||||||
|
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 }}"
|
||||||
|
|
||||||
|
# Warning fields
|
||||||
|
warning_description: "{{ trigger.event.data.warning | default('unknown') }}"
|
||||||
|
warning_code: "{{ trigger.event.data.code | default('') }}"
|
||||||
|
|
||||||
|
# Error fields
|
||||||
|
error_description: "{{ trigger.event.data.error | default('unknown') }}"
|
||||||
|
error_code: "{{ trigger.event.data.code | default('') }}"
|
||||||
|
|
||||||
|
# Information fields
|
||||||
|
information_description: "{{ (trigger.event.data.information | default('unknown')) | replace('_', ' ') | title }}"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CONDITIONS
|
||||||
|
# =============================================================================
|
||||||
|
condition:
|
||||||
|
# Only process events from the configured vacuum entity
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ event_entity_id == vacuum_entity }}"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ACTIONS
|
||||||
|
# =============================================================================
|
||||||
|
action:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Debug Logging
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Dreame Vacuum Debug"
|
||||||
|
message: >
|
||||||
|
**Trigger:** {{ trigger.id }}
|
||||||
|
**Entity:** {{ event_entity_id }}
|
||||||
|
**Vacuum:** {{ vacuum_name }}
|
||||||
|
**Event Data:** {{ trigger.event.data }}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Send Notification Based on Event Type
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- choose:
|
||||||
|
|
||||||
|
# CASE 1: Cleaning Started
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'task_status'
|
||||||
|
and not task_completed
|
||||||
|
and enable_cleaning_started }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_cleaning_started_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{cleaning_mode}', task_cleaning_mode)
|
||||||
|
| replace('{status}', task_status_value) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
|
|
||||||
|
# CASE 2: Cleaning Completed
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'task_status'
|
||||||
|
and task_completed
|
||||||
|
and enable_cleaning_completed }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_cleaning_completed_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{cleaning_mode}', task_cleaning_mode)
|
||||||
|
| replace('{status}', task_status_value)
|
||||||
|
| replace('{cleaned_area}', task_cleaned_area | string)
|
||||||
|
| replace('{cleaning_time}', task_cleaning_time | string) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
|
|
||||||
|
# CASE 3: Consumable Depleted
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'consumable' and enable_consumable }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_consumable_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{consumable}', consumable_name) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
|
|
||||||
|
# CASE 4: Warning
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'warning' and enable_warning }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_warning_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{warning}', warning_description)
|
||||||
|
| replace('{code}', warning_code | string) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
|
|
||||||
|
# CASE 5: Error
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'error' and enable_error }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_error_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{error}', error_description)
|
||||||
|
| replace('{code}', error_code | string) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
|
|
||||||
|
# CASE 6: Information
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: >
|
||||||
|
{{ trigger.id == 'information' and enable_information }}
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
message: >
|
||||||
|
{% set tpl = message_information_template %}
|
||||||
|
{{ tpl | replace('{vacuum_name}', vacuum_name)
|
||||||
|
| replace('{information}', information_description) }}
|
||||||
|
- service: notify.send_message
|
||||||
|
target:
|
||||||
|
entity_id: "{{ notify_targets }}"
|
||||||
|
data:
|
||||||
|
message: "{{ message }}"
|
||||||
@@ -107,6 +107,7 @@ These variables can be used in the image and video asset templates. Also used fo
|
|||||||
| `{type}` | Asset type (IMAGE or VIDEO) |
|
| `{type}` | Asset type (IMAGE or VIDEO) |
|
||||||
| `{created}` | Creation date/time (always shown) |
|
| `{created}` | Creation date/time (always shown) |
|
||||||
| `{created_if_unique}` | Creation date/time formatted with template if dates differ, empty if all same |
|
| `{created_if_unique}` | Creation date/time formatted with template if dates differ, empty if all same |
|
||||||
|
| `{year}` | Year the asset was created (4-digit, e.g., 2024) |
|
||||||
| `{owner}` | Owner display name |
|
| `{owner}` | Owner display name |
|
||||||
| `{url}` | Public URL to view the asset (empty if no shared link) |
|
| `{url}` | Public URL to view the asset (empty if no shared link) |
|
||||||
| `{download_url}` | Direct download URL for the original file |
|
| `{download_url}` | Direct download URL for the original file |
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ blueprint:
|
|||||||
name: "Image Asset Template"
|
name: "Image Asset Template"
|
||||||
description: >
|
description: >
|
||||||
Template for IMAGE assets in the list. Also used for Telegram media captions.
|
Template for IMAGE assets in the list. Also used for Telegram media captions.
|
||||||
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
|
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{year}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
|
||||||
default: "\n • 🖼️ {filename}"
|
default: "\n • 🖼️ {filename}"
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
@@ -246,7 +246,7 @@ blueprint:
|
|||||||
name: "Video Asset Template"
|
name: "Video Asset Template"
|
||||||
description: >
|
description: >
|
||||||
Template for VIDEO assets in the list. Also used for Telegram media captions.
|
Template for VIDEO assets in the list. Also used for Telegram media captions.
|
||||||
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
|
Variables: `{filename}`, `{description}`, `{type}`, `{created}`, `{created_if_unique}`, `{year}`, `{owner}`, `{url}`, `{download_url}`, `{photo_url}`, `{playback_url}`, `{people}`, `{album_name}`, `{album_created}`, `{album_updated}`, `{is_favorite}`, `{rating}`, `{location}`, `{location_if_unique}`, `{city}`, `{state}`, `{country}`
|
||||||
default: "\n • 🎬 {filename}"
|
default: "\n • 🎬 {filename}"
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
@@ -1160,6 +1160,7 @@ variables:
|
|||||||
{% set raw_date = asset.created_at | default('', true) %}
|
{% set raw_date = asset.created_at | default('', true) %}
|
||||||
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
||||||
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
||||||
|
{% set year = dt.strftime('%Y') if dt else '' %}
|
||||||
{% set created_if_unique = '' if unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date)) %}
|
{% set created_if_unique = '' if unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date)) %}
|
||||||
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
||||||
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
||||||
@@ -1190,7 +1191,8 @@ variables:
|
|||||||
| replace('{location_if_unique}', location_if_unique)
|
| replace('{location_if_unique}', location_if_unique)
|
||||||
| replace('{city}', city)
|
| replace('{city}', city)
|
||||||
| replace('{state}', state)
|
| replace('{state}', state)
|
||||||
| replace('{country}', country) %}
|
| replace('{country}', country)
|
||||||
|
| replace('{year}', year) %}
|
||||||
{% set ns.items = ns.items ~ item %}
|
{% set ns.items = ns.items ~ item %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% set more_count = sorted_assets | length - max_items %}
|
{% set more_count = sorted_assets | length - max_items %}
|
||||||
@@ -1565,6 +1567,7 @@ action:
|
|||||||
{% set raw_date = asset.created_at | default('', true) %}
|
{% set raw_date = asset.created_at | default('', true) %}
|
||||||
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
||||||
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
||||||
|
{% set year = dt.strftime('%Y') if dt else '' %}
|
||||||
{% set created_if_unique = '' if scheduled_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
{% set created_if_unique = '' if scheduled_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
||||||
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
||||||
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
||||||
@@ -1595,7 +1598,8 @@ action:
|
|||||||
| replace('{location_if_unique}', location_if_unique)
|
| replace('{location_if_unique}', location_if_unique)
|
||||||
| replace('{city}', city)
|
| replace('{city}', city)
|
||||||
| replace('{state}', state)
|
| replace('{state}', state)
|
||||||
| replace('{country}', country) %}
|
| replace('{country}', country)
|
||||||
|
| replace('{year}', year) %}
|
||||||
{% set ns.items = ns.items ~ item %}
|
{% set ns.items = ns.items ~ item %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ ns.items }}
|
{{ ns.items }}
|
||||||
@@ -1824,6 +1828,7 @@ action:
|
|||||||
{% set raw_date = asset.created_at | default('', true) %}
|
{% set raw_date = asset.created_at | default('', true) %}
|
||||||
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
||||||
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
||||||
|
{% set year = dt.strftime('%Y') if dt else '' %}
|
||||||
{% set created_if_unique = '' if combined_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
{% set created_if_unique = '' if combined_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
||||||
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
||||||
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
||||||
@@ -1854,7 +1859,8 @@ action:
|
|||||||
| replace('{location_if_unique}', location_if_unique)
|
| replace('{location_if_unique}', location_if_unique)
|
||||||
| replace('{city}', city)
|
| replace('{city}', city)
|
||||||
| replace('{state}', state)
|
| replace('{state}', state)
|
||||||
| replace('{country}', country) %}
|
| replace('{country}', country)
|
||||||
|
| replace('{year}', year) %}
|
||||||
{% set ns.items = ns.items ~ item %}
|
{% set ns.items = ns.items ~ item %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ ns.items }}
|
{{ ns.items }}
|
||||||
@@ -2087,6 +2093,7 @@ action:
|
|||||||
{% set raw_date = asset.created_at | default('', true) %}
|
{% set raw_date = asset.created_at | default('', true) %}
|
||||||
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
||||||
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
||||||
|
{% set year = dt.strftime('%Y') if dt else '' %}
|
||||||
{% set created_if_unique = '' if memory_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
{% set created_if_unique = '' if memory_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
||||||
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
||||||
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
||||||
@@ -2117,7 +2124,8 @@ action:
|
|||||||
| replace('{location_if_unique}', location_if_unique)
|
| replace('{location_if_unique}', location_if_unique)
|
||||||
| replace('{city}', city)
|
| replace('{city}', city)
|
||||||
| replace('{state}', state)
|
| replace('{state}', state)
|
||||||
| replace('{country}', country) %}
|
| replace('{country}', country)
|
||||||
|
| replace('{year}', year) %}
|
||||||
{% set ns.items = ns.items ~ item %}
|
{% set ns.items = ns.items ~ item %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ ns.items }}
|
{{ ns.items }}
|
||||||
@@ -2336,6 +2344,7 @@ action:
|
|||||||
{% set raw_date = asset.created_at | default('', true) %}
|
{% set raw_date = asset.created_at | default('', true) %}
|
||||||
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
{% set dt = raw_date | as_datetime(none) if raw_date is string and raw_date | length > 0 else none %}
|
||||||
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
{% set formatted_date = dt.strftime(date_format) if dt else '' %}
|
||||||
|
{% set year = dt.strftime('%Y') if dt else '' %}
|
||||||
{% set created_if_unique = '' if memory_comb_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
{% set created_if_unique = '' if memory_comb_unique_dates | length == 1 else (date_if_unique_template | replace('{date}', formatted_date) if formatted_date | length > 0 else '') %}
|
||||||
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
{% set is_favorite = favorite_indicator_template if asset.is_favorite | default(false) else '' %}
|
||||||
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
{% set rating = asset.rating | default('') | string if asset.rating not in [none, ''] else '' %}
|
||||||
@@ -2366,7 +2375,8 @@ action:
|
|||||||
| replace('{location_if_unique}', location_if_unique)
|
| replace('{location_if_unique}', location_if_unique)
|
||||||
| replace('{city}', city)
|
| replace('{city}', city)
|
||||||
| replace('{state}', state)
|
| replace('{state}', state)
|
||||||
| replace('{country}', country) %}
|
| replace('{country}', country)
|
||||||
|
| replace('{year}', year) %}
|
||||||
{% set ns.items = ns.items ~ item %}
|
{% set ns.items = ns.items ~ item %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{{ ns.items }}
|
{{ ns.items }}
|
||||||
|
|||||||
67
Common/Light Color Mapper/README.md
Normal file
67
Common/Light Color Mapper/README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Light Color Mapper
|
||||||
|
|
||||||
|
Syncs light colors in real-time from color sensor entities. Sensors and lights are paired by list index (sensor 0 → light 0, sensor 1 → light 1, etc.).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Real-time color sync from sensor entities to lights
|
||||||
|
- Index-based sensor-to-light mapping (add in matching order)
|
||||||
|
- FPS throttling for performance control (0–60 fps)
|
||||||
|
- Brightness override or per-sensor brightness from attributes
|
||||||
|
- Configurable behavior when sensors are unavailable (turn off, keep last, default color)
|
||||||
|
- Control entity (switch/input_boolean) to enable/disable
|
||||||
|
- Optional auto-off when the control entity is disabled
|
||||||
|
- Custom post-update callback action (runs after each update cycle)
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The automation triggers on any state change from the configured color sensors. Each sensor is paired with a light by list position:
|
||||||
|
|
||||||
|
| Sensor Index | Light Index |
|
||||||
|
| --- | --- |
|
||||||
|
| Sensor 0 | Light 0 |
|
||||||
|
| Sensor 1 | Light 1 |
|
||||||
|
| Sensor 2 | Light 2 |
|
||||||
|
| ... | ... |
|
||||||
|
|
||||||
|
When a sensor updates, **all** sensor-light pairs are re-evaluated to keep lights in sync.
|
||||||
|
|
||||||
|
### FPS Throttling
|
||||||
|
|
||||||
|
The automation uses `mode: restart` with a trailing delay to throttle updates:
|
||||||
|
|
||||||
|
- **FPS = 0**: No throttle — every state change triggers an immediate update.
|
||||||
|
- **FPS > 0**: After processing all lights, a delay of `1000 / FPS` ms is added. If a new sensor state arrives during the delay, the automation restarts with the latest data. This naturally caps updates to the configured FPS.
|
||||||
|
|
||||||
|
### Sensor Requirements
|
||||||
|
|
||||||
|
Each color sensor should expose:
|
||||||
|
|
||||||
|
| Attribute | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| **State** | Hex color string (e.g., `#FF8800`) or `None` when not processing |
|
||||||
|
| `rgb_color` | Color as `[r, g, b]` list (used to set light color) |
|
||||||
|
| `brightness` | Brightness value 0–255 (used when no override is set) |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Input | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| **Control Entity** | Switch or input_boolean that enables/disables the automation |
|
||||||
|
| **Turn Off Lights on Disable** | Turn off all mapped lights when the control entity is switched off (default: true) |
|
||||||
|
| **Color Sensors** | List of color sensor entities (index-mapped to lights) |
|
||||||
|
| **Lights** | List of light entities (index-mapped to sensors) |
|
||||||
|
| **Maximum FPS** | Update rate limit, 0 = unlimited (default: 0) |
|
||||||
|
| **Brightness Override** | Fixed brightness for all lights, 0 = use sensor brightness (default: 0) |
|
||||||
|
| **When Sensor is Unavailable** | Action when sensor is None/unavailable: Turn Off, Keep Last, or Set Default Color |
|
||||||
|
| **Default Color** | Color to apply when sensor is unavailable and action is "Set Default Color" |
|
||||||
|
| **Post-Update Action** | Custom action to run after all lights are updated (e.g., fire an event, call a service) |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- If the sensor and light lists have different lengths, only the shorter count of pairs is used (extra entities are ignored).
|
||||||
|
- When the control entity is turned off and "Turn Off Lights on Disable" is enabled, only the paired lights (up to `pair_count`) are turned off.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Alexei Dolgolyov (<dolgolyov.alexei@gmail.com>)
|
||||||
327
Common/Light Color Mapper/blueprint.yaml
Normal file
327
Common/Light Color Mapper/blueprint.yaml
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# Light Color Mapper
|
||||||
|
# Syncs light colors in real-time from color sensor entities.
|
||||||
|
# See README.md for detailed documentation.
|
||||||
|
#
|
||||||
|
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
|
||||||
|
|
||||||
|
blueprint:
|
||||||
|
name: "Custom: Light Color Mapper"
|
||||||
|
description: >
|
||||||
|
Maps color sensor states to lights in real-time.
|
||||||
|
Sensors and lights are paired by list index (sensor 0 → light 0, etc.).
|
||||||
|
Supports FPS throttling, brightness override, and configurable behavior
|
||||||
|
when sensors are unavailable.
|
||||||
|
domain: automation
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# INPUT CONFIGURATION
|
||||||
|
# ===========================================================================
|
||||||
|
input:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Control
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
control_group:
|
||||||
|
name: "Control"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
control_entity:
|
||||||
|
name: Control Entity
|
||||||
|
description: "Switch or input_boolean that enables/disables this automation."
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain:
|
||||||
|
- input_boolean
|
||||||
|
- switch
|
||||||
|
|
||||||
|
turn_off_on_disable:
|
||||||
|
name: Turn Off Lights on Disable
|
||||||
|
description: "Turn off all mapped lights when the control entity is switched off."
|
||||||
|
default: true
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Devices
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
devices_group:
|
||||||
|
name: "Devices"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
color_sensors:
|
||||||
|
name: Color Sensors
|
||||||
|
description: >
|
||||||
|
List of color sensor entities. Each sensor's state is a hex color
|
||||||
|
string (e.g., #FF8800) with attributes: r, g, b, brightness,
|
||||||
|
rgb_color. Sensors are mapped to lights by list index.
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: sensor
|
||||||
|
multiple: true
|
||||||
|
|
||||||
|
lights:
|
||||||
|
name: Lights
|
||||||
|
description: >
|
||||||
|
List of light entities. Each light is paired with the sensor at the
|
||||||
|
same list index (sensor 0 → light 0, sensor 1 → light 1, etc.).
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: light
|
||||||
|
multiple: true
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Settings
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
settings_group:
|
||||||
|
name: "Settings"
|
||||||
|
collapsed: false
|
||||||
|
input:
|
||||||
|
max_fps:
|
||||||
|
name: Maximum FPS
|
||||||
|
description: >
|
||||||
|
Maximum updates per second. With mode: restart, a trailing delay
|
||||||
|
throttles updates naturally — new state changes during the delay
|
||||||
|
restart the automation with fresh data. Set to 0 for unlimited.
|
||||||
|
default: 0
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 60
|
||||||
|
mode: slider
|
||||||
|
|
||||||
|
brightness_override:
|
||||||
|
name: Brightness Override
|
||||||
|
description: >
|
||||||
|
Override brightness for all lights (0–255).
|
||||||
|
Set to 0 to use each sensor's brightness attribute instead.
|
||||||
|
default: 0
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 255
|
||||||
|
mode: slider
|
||||||
|
|
||||||
|
none_action:
|
||||||
|
name: When Sensor is Unavailable
|
||||||
|
description: "Action to take when a sensor state is None, unknown, or unavailable."
|
||||||
|
default: "turn_off"
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- label: "Turn Off Light"
|
||||||
|
value: "turn_off"
|
||||||
|
- label: "Keep Last Color"
|
||||||
|
value: "keep_last"
|
||||||
|
- label: "Set Default Color"
|
||||||
|
value: "default_color"
|
||||||
|
|
||||||
|
default_color:
|
||||||
|
name: Default Color
|
||||||
|
description: >
|
||||||
|
Color to apply when a sensor is unavailable.
|
||||||
|
Only used when "When Sensor is Unavailable" is set to "Set Default Color".
|
||||||
|
default: [255, 255, 255]
|
||||||
|
selector:
|
||||||
|
color_rgb:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Callback Actions
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
actions_group:
|
||||||
|
name: "Callback Actions"
|
||||||
|
collapsed: true
|
||||||
|
input:
|
||||||
|
post_update_action:
|
||||||
|
name: Post-Update Action
|
||||||
|
description: >
|
||||||
|
Custom action to run after all sensor-light pairs have been processed.
|
||||||
|
Runs once per update cycle, after all lights are set.
|
||||||
|
Leave empty to do nothing.
|
||||||
|
default: []
|
||||||
|
selector:
|
||||||
|
action:
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Debug
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
debug:
|
||||||
|
name: "Debug"
|
||||||
|
collapsed: true
|
||||||
|
input:
|
||||||
|
enable_debug_notifications:
|
||||||
|
name: Enable Debug Notifications
|
||||||
|
description: >
|
||||||
|
Send persistent notifications showing sensor states and light
|
||||||
|
updates for troubleshooting.
|
||||||
|
default: false
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
|
||||||
|
# Restart mode ensures the latest sensor state always wins.
|
||||||
|
# Combined with a trailing delay (FPS throttle), incoming state changes
|
||||||
|
# during the delay cancel the current run and restart with fresh data.
|
||||||
|
mode: restart
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TRIGGERS
|
||||||
|
# =============================================================================
|
||||||
|
trigger:
|
||||||
|
# Any color sensor changes state
|
||||||
|
- platform: state
|
||||||
|
entity_id: !input color_sensors
|
||||||
|
id: "sensor_changed"
|
||||||
|
|
||||||
|
# Control entity toggled on/off
|
||||||
|
- platform: state
|
||||||
|
entity_id: !input control_entity
|
||||||
|
id: "control_changed"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VARIABLES
|
||||||
|
# =============================================================================
|
||||||
|
variables:
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Input References
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
control_entity: !input control_entity
|
||||||
|
turn_off_on_disable: !input turn_off_on_disable
|
||||||
|
color_sensors: !input color_sensors
|
||||||
|
lights: !input lights
|
||||||
|
max_fps: !input max_fps
|
||||||
|
brightness_override: !input brightness_override
|
||||||
|
none_action: !input none_action
|
||||||
|
default_color: !input default_color
|
||||||
|
enable_debug_notifications: !input enable_debug_notifications
|
||||||
|
post_update_action: !input post_update_action
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Computed Values
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Use the shorter list length to avoid index-out-of-range errors
|
||||||
|
pair_count: "{{ [color_sensors | length, lights | length] | min }}"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ACTIONS
|
||||||
|
# =============================================================================
|
||||||
|
action:
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Debug Logging
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ enable_debug_notifications }}"
|
||||||
|
sequence:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "Light Color Mapper Debug"
|
||||||
|
message: >
|
||||||
|
**Trigger:** {{ trigger.id }}
|
||||||
|
**Control:** {{ states(control_entity) }}
|
||||||
|
**Pairs:** {{ pair_count }}
|
||||||
|
**FPS:** {{ max_fps }}
|
||||||
|
**Brightness Override:** {{ brightness_override }}
|
||||||
|
|
||||||
|
**Sensor States:**
|
||||||
|
{% for i in range(pair_count | int) %}
|
||||||
|
{{ i }}: {{ color_sensors[i] }} = {{ states(color_sensors[i]) }} (rgb: {{ state_attr(color_sensors[i], 'rgb_color') }}, brightness: {{ state_attr(color_sensors[i], 'brightness') }})
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Check Control Entity
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ not is_state(control_entity, 'on') }}"
|
||||||
|
sequence:
|
||||||
|
# Turn off all mapped lights if configured
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ turn_off_on_disable }}"
|
||||||
|
sequence:
|
||||||
|
- service: light.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ lights[:pair_count | int] }}"
|
||||||
|
- stop: "Control entity is off"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Process Each Sensor-Light Pair
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- repeat:
|
||||||
|
count: "{{ pair_count | int }}"
|
||||||
|
sequence:
|
||||||
|
- variables:
|
||||||
|
idx: "{{ repeat.index - 1 }}"
|
||||||
|
current_sensor: "{{ color_sensors[idx] }}"
|
||||||
|
current_light: "{{ lights[idx] }}"
|
||||||
|
sensor_state: "{{ states(current_sensor) }}"
|
||||||
|
is_unavailable: "{{ sensor_state in ['None', 'none', 'unknown', 'unavailable', 'undefined'] or sensor_state | length == 0 }}"
|
||||||
|
|
||||||
|
- choose:
|
||||||
|
# Sensor is unavailable — apply configured action
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ is_unavailable }}"
|
||||||
|
sequence:
|
||||||
|
- choose:
|
||||||
|
# Turn off the light
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ none_action == 'turn_off' }}"
|
||||||
|
sequence:
|
||||||
|
- service: light.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: "{{ current_light }}"
|
||||||
|
|
||||||
|
# Set default color
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ none_action == 'default_color' }}"
|
||||||
|
sequence:
|
||||||
|
- service: light.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: "{{ current_light }}"
|
||||||
|
data:
|
||||||
|
rgb_color: "{{ default_color }}"
|
||||||
|
brightness: "{{ brightness_override | int if brightness_override | int > 0 else 255 }}"
|
||||||
|
|
||||||
|
# keep_last — do nothing
|
||||||
|
|
||||||
|
# Sensor has valid state — apply color to light
|
||||||
|
default:
|
||||||
|
- variables:
|
||||||
|
sensor_rgb: "{{ state_attr(current_sensor, 'rgb_color') | default([255, 255, 255]) }}"
|
||||||
|
sensor_brightness: "{{ state_attr(current_sensor, 'brightness') | default(255) }}"
|
||||||
|
target_brightness: "{{ brightness_override | int if brightness_override | int > 0 else sensor_brightness | int }}"
|
||||||
|
|
||||||
|
- service: light.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: "{{ current_light }}"
|
||||||
|
data:
|
||||||
|
rgb_color: "{{ sensor_rgb }}"
|
||||||
|
brightness: "{{ target_brightness }}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Post-Update Callback
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ post_update_action | length > 0 }}"
|
||||||
|
sequence: !input post_update_action
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# FPS Throttle
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# With mode: restart, new triggers during this delay cancel and restart
|
||||||
|
# the automation, effectively capping updates to max_fps per second.
|
||||||
|
- choose:
|
||||||
|
- conditions:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{{ max_fps | int > 0 }}"
|
||||||
|
sequence:
|
||||||
|
- delay:
|
||||||
|
milliseconds: "{{ (1000 / (max_fps | int)) | int }}"
|
||||||
@@ -17,7 +17,7 @@ This blueprint creates a smart motion-activated light control system. It handles
|
|||||||
- Day/Night mode (different light settings based on time)
|
- Day/Night mode (different light settings based on time)
|
||||||
- Scene support (activate scenes instead of light parameters)
|
- Scene support (activate scenes instead of light parameters)
|
||||||
- Dim before off (visual warning before turning off)
|
- Dim before off (visual warning before turning off)
|
||||||
- Manual override detection (stops automation if user changes light)
|
- Manual override detection (stops automation if user changes light) with configurable grace period
|
||||||
- Brightness threshold (only trigger if light is dim)
|
- Brightness threshold (only trigger if light is dim)
|
||||||
- Custom light parameters (brightness, color, etc.)
|
- Custom light parameters (brightness, color, etc.)
|
||||||
- Callback actions for enable/disable/manual events
|
- Callback actions for enable/disable/manual events
|
||||||
@@ -37,7 +37,8 @@ The automation tracks these states via persistent storage:
|
|||||||
## Behavior Notes
|
## Behavior Notes
|
||||||
|
|
||||||
- Will NOT turn on light if it's already ON (prevents hijacking user control)
|
- Will NOT turn on light if it's already ON (prevents hijacking user control)
|
||||||
- If user changes light while automation is active, enters MANUAL mode
|
- If user changes light while automation is active, enters MANUAL mode (after grace period)
|
||||||
|
- Grace period prevents false manual overrides from delayed device state reports (e.g., Zigbee)
|
||||||
- MANUAL mode exits when light is turned OFF (by any means)
|
- MANUAL mode exits when light is turned OFF (by any means)
|
||||||
- Timeout delay only applies when turning OFF (motion cleared)
|
- Timeout delay only applies when turning OFF (motion cleared)
|
||||||
- Time conditions support overnight windows (e.g., 22:00 to 06:00)
|
- Time conditions support overnight windows (e.g., 22:00 to 06:00)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ blueprint:
|
|||||||
- switch
|
- switch
|
||||||
- group
|
- group
|
||||||
- light
|
- light
|
||||||
|
- input_boolean
|
||||||
multiple: true
|
multiple: true
|
||||||
|
|
||||||
condition_switches:
|
condition_switches:
|
||||||
@@ -384,6 +385,22 @@ blueprint:
|
|||||||
step: 1
|
step: 1
|
||||||
unit_of_measurement: "seconds"
|
unit_of_measurement: "seconds"
|
||||||
|
|
||||||
|
manual_override_grace_period:
|
||||||
|
name: Manual Override Grace Period (seconds)
|
||||||
|
description: >
|
||||||
|
After the automation turns on a light, ignore state changes for
|
||||||
|
this many seconds to avoid false manual override detection.
|
||||||
|
Some devices (especially Zigbee) report delayed state updates
|
||||||
|
that can be mistaken for manual control. Increase this value
|
||||||
|
if you see false manual overrides in the debug log.
|
||||||
|
default: 10
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 30
|
||||||
|
step: 1
|
||||||
|
unit_of_measurement: "seconds"
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Debug
|
# Debug
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -546,6 +563,7 @@ variables:
|
|||||||
min_on_duration: !input min_on_duration
|
min_on_duration: !input min_on_duration
|
||||||
brightness_threshold: !input brightness_threshold
|
brightness_threshold: !input brightness_threshold
|
||||||
transition_duration: !input transition_duration
|
transition_duration: !input transition_duration
|
||||||
|
manual_override_grace_period: !input manual_override_grace_period
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Target Device Resolution
|
# Target Device Resolution
|
||||||
@@ -591,11 +609,22 @@ variables:
|
|||||||
# Reference light for state checks (first available)
|
# Reference light for state checks (first available)
|
||||||
reference_light: "{{ resolved_all_lights[0] if resolved_all_lights | length > 0 else none }}"
|
reference_light: "{{ resolved_all_lights[0] if resolved_all_lights | length > 0 else none }}"
|
||||||
|
|
||||||
# Check if any device is on
|
# Check if any device is on (respects brightness_threshold)
|
||||||
any_device_on: >
|
any_device_on: >
|
||||||
{% set lights_on = resolved_all_lights | select('is_state', 'on') | list | length > 0 %}
|
{% set ns = namespace(lights_on=false) %}
|
||||||
|
{% for light in resolved_all_lights %}
|
||||||
|
{% if is_state(light, 'on') %}
|
||||||
|
{% if brightness_threshold | int(0) > 0 %}
|
||||||
|
{% if state_attr(light, 'brightness') | int(0) >= brightness_threshold | int(0) %}
|
||||||
|
{% set ns.lights_on = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% set ns.lights_on = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
{% set switches_on = resolved_all_switches | select('is_state', 'on') | list | length > 0 %}
|
{% set switches_on = resolved_all_switches | select('is_state', 'on') | list | length > 0 %}
|
||||||
{{ lights_on or switches_on }}
|
{{ ns.lights_on or switches_on }}
|
||||||
|
|
||||||
all_devices_off: "{{ not any_device_on }}"
|
all_devices_off: "{{ not any_device_on }}"
|
||||||
|
|
||||||
@@ -884,9 +913,20 @@ action:
|
|||||||
|
|
||||||
# ----- Sub-case: User manually changed the light -----
|
# ----- Sub-case: User manually changed the light -----
|
||||||
# Transition from ENABLED to MANUAL (user took control)
|
# Transition from ENABLED to MANUAL (user took control)
|
||||||
|
# Grace period: ignore state changes shortly after the automation
|
||||||
|
# turns on the light to avoid false manual override detection.
|
||||||
|
# Some devices (especially Zigbee) report delayed state updates.
|
||||||
- conditions:
|
- conditions:
|
||||||
- condition: template
|
- condition: template
|
||||||
value_template: "{{ state_is_enabled }}"
|
value_template: >
|
||||||
|
{% set last_ts = automation_state.get(state_motion_light_last_action_timestamp, none) %}
|
||||||
|
{% set grace = (transition_duration | float(0)) + (manual_override_grace_period | float(10)) %}
|
||||||
|
{% if state_is_enabled and last_ts is not none %}
|
||||||
|
{% set parsed = last_ts | as_datetime %}
|
||||||
|
{{ parsed is none or (now() - parsed).total_seconds() > grace }}
|
||||||
|
{% else %}
|
||||||
|
{{ state_is_enabled }}
|
||||||
|
{% endif %}
|
||||||
sequence:
|
sequence:
|
||||||
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
|
# BUG FIX: Fixed YAML structure - was 'data: >' instead of 'data:' with 'value: >'
|
||||||
- service: input_text.set_value
|
- service: input_text.set_value
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0"
|
"version": "2.2.3"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user