blueprint: name: Bambu printer – finish / fault notify with snapshot (v4.1) description: > Sends a Home Assistant mobile notification with a camera snapshot when a Bambu printer finishes or faults. **Features:** - Optional light activation before snapshot capture - Text-to-speech announcements on completion/fault - Custom actions on print completion (turn on lights, switches, run scripts, etc.) - Custom actions on print fault - Truly optional critical notifications (use "never" to disable completely) - Snapshot delay for light warmup / camera adjustment - Critical sound window with proper time handling - Cooldown between alerts - Stage/progress guards - Persistent notification for faults - Per-printer enable switch --- If you find this useful, [buy me a coffee!](https://buymeacoffee.com/hallyaus) --- **v4.1 Changes:** - Fixed spurious notifications when printer reconnects from offline state - Added 'from' constraints on state triggers to prevent false notifications - Added guard condition to reject triggers from offline/unavailable states **v4 Changes:** - Added TTS (text-to-speech) announcements for success and fault events - Configurable TTS messages with template support - Optional quiet hours to suppress TTS announcements - Support for any media_player or tts service **v3 Changes:** - Added custom actions on success (e.g., turn on celebration light, activate switch) - Added custom actions on fault (e.g., turn on warning light, pause other printers) - Actions are fully customizable - use any Home Assistant action **v2 Changes:** - Added optional light entity to turn on before snapshot - Fixed critical notifications - now truly optional with "never" option - Added snapshot delay input (positive seconds only) - Improved time window handling (00:00-00:00 now means "never") - Better error handling and validation domain: automation source_url: https://github.com/HallyAus/homeassistant-bambu-blueprints homeassistant: min_version: 2024.6.0 input: # ══════════════════════════════════════════════════════════════════════════ # PRINTER SENSORS # ══════════════════════════════════════════════════════════════════════════ printer_sensors: name: Printer Sensors icon: mdi:printer-3d description: Configure your Bambu printer's sensor entities collapsed: false input: print_status_sensor: name: Print status sensor description: Sensor with states like running / finish / failed. selector: entity: domain: sensor print_error_binary: name: Print error binary sensor description: Binary sensor that goes ON on print error. selector: entity: domain: binary_sensor current_stage_sensor: name: Current stage sensor description: Enum sensor for printer stage (printing, inspecting_first_layer, etc.). selector: entity: domain: sensor progress_sensor: name: Progress sensor description: Sensor for print progress (%). selector: entity: domain: sensor printer_name_sensor: name: Printer name sensor description: Sensor with the printer name (e.g. R2D7). selector: entity: domain: sensor task_name_sensor: name: Task name sensor description: Sensor with current task/job name. selector: entity: domain: sensor print_weight_sensor: name: Print weight sensor description: Sensor with print weight in grams. selector: entity: domain: sensor camera: name: Printer camera selector: entity: domain: camera # ══════════════════════════════════════════════════════════════════════════ # SNAPSHOT SETTINGS # ══════════════════════════════════════════════════════════════════════════ snapshot_settings: name: Snapshot Settings icon: mdi:camera description: Configure snapshot capture behavior collapsed: true input: snapshot_light: name: Snapshot light (optional) description: > Light to turn on before taking the snapshot. Leave empty to skip. The light will be turned on, snapshot taken, then restored to previous state. default: [] selector: entity: domain: light multiple: false snapshot_light_brightness: name: Snapshot light brightness (%) description: Brightness level for the snapshot light (1-100%). default: 100 selector: number: min: 1 max: 100 unit_of_measurement: "%" mode: slider snapshot_delay_seconds: name: Snapshot delay (seconds) description: > Seconds to wait before taking snapshot. Use to allow light to warm up or camera to adjust. Must be 0 or positive. default: 1 selector: number: min: 0 max: 30 unit_of_measurement: seconds mode: slider # ══════════════════════════════════════════════════════════════════════════ # NOTIFICATION SETTINGS # ══════════════════════════════════════════════════════════════════════════ notification_settings: name: Notification Settings icon: mdi:bell description: Configure mobile and persistent notifications collapsed: false input: notifications_enabled_boolean: name: Notifications enabled boolean description: input_boolean that must be ON to send notifications. selector: entity: domain: input_boolean notify_device: name: Notify device/service description: > Enter notify service name (e.g., mobile_app_your_phone) or leave empty to disable mobile notifications. default: "" selector: text: # ══════════════════════════════════════════════════════════════════════════ # TTS ANNOUNCEMENT SETTINGS # ══════════════════════════════════════════════════════════════════════════ tts_settings: name: TTS Announcements icon: mdi:bullhorn description: Configure text-to-speech announcements for print events collapsed: true input: tts_enable: name: Enable TTS announcements description: Turn on to enable voice announcements for print events. default: false selector: boolean: {} tts_service: name: TTS service description: > The TTS service to use for announcements. Examples: - tts.google_translate_say - tts.cloud_say - tts.piper - tts.amazon_polly Leave empty to use the default tts.speak service. default: "tts.speak" selector: text: {} tts_media_player: name: Media player for announcements description: > Select the media player(s) to announce on. Can be a single speaker, a group, or multiple devices. default: [] selector: entity: domain: media_player multiple: true tts_volume: name: Announcement volume (%) description: > Volume level for TTS announcements (1-100%). Set to 0 to use the current volume. default: 0 selector: number: min: 0 max: 100 unit_of_measurement: "%" mode: slider tts_success_message: name: Success announcement message description: > Message to announce when print completes successfully. Available variables: - {{ printer_name }} - Name of the printer - {{ task_name }} - Name of the print job - {{ print_weight }} - Weight in grams - {{ progress }} - Progress percentage default: "{{ printer_name }} has finished printing {{ task_name }}" selector: text: multiline: true tts_fault_message: name: Fault announcement message description: > Message to announce when print fails. Available variables: - {{ printer_name }} - Name of the printer - {{ task_name }} - Name of the print job - {{ progress }} - Progress percentage - {{ status }} - Current status default: "Warning! {{ printer_name }} has encountered a fault while printing {{ task_name }}" selector: text: multiline: true tts_quiet_hours_enable: name: Enable TTS quiet hours description: Suppress TTS announcements during specified hours. default: false selector: boolean: {} tts_quiet_hours_start: name: TTS quiet hours start description: Time to start suppressing TTS announcements (e.g., bedtime). default: "22:00:00" selector: time: {} tts_quiet_hours_end: name: TTS quiet hours end description: Time to stop suppressing TTS announcements (e.g., morning). default: "07:00:00" selector: time: {} # ══════════════════════════════════════════════════════════════════════════ # SUCCESS NOTIFICATION SETTINGS # ══════════════════════════════════════════════════════════════════════════ success_notification_settings: name: Success Notification Settings icon: mdi:check-circle description: Configure notifications for successful prints collapsed: true input: success_notification_type: name: Success notification type description: > Notification priority for successful prints. - **normal**: Standard notification (always) - **critical**: Critical notification during specified time window - **never_critical**: Always use normal notifications (ignores time window) default: normal selector: select: options: - label: "Normal (standard notifications)" value: "normal" - label: "Critical (during time window)" value: "critical" - label: "Never critical (always normal)" value: "never_critical" success_critical_start: name: Success critical window start description: > Time from which success notifications become critical. Only applies if notification type is "Critical". default: "00:00:00" selector: time: {} success_critical_end: name: Success critical window end description: > Time after which success notifications stop being critical. Only applies if notification type is "Critical". Set both start and end to the same time for critical ALL DAY. default: "00:00:00" selector: time: {} # ══════════════════════════════════════════════════════════════════════════ # FAULT NOTIFICATION SETTINGS # ══════════════════════════════════════════════════════════════════════════ fault_notification_settings: name: Fault Notification Settings icon: mdi:alert-circle description: Configure notifications for failed prints collapsed: true input: fault_notification_type: name: Fault notification type description: > Notification priority for failed prints. - **normal**: Standard notification (always) - **critical**: Critical notification during specified time window - **never_critical**: Always use normal notifications (ignores time window) default: critical selector: select: options: - label: "Normal (standard notifications)" value: "normal" - label: "Critical (during time window)" value: "critical" - label: "Never critical (always normal)" value: "never_critical" fault_critical_start: name: Fault critical window start description: > Time from which fault notifications become critical. Only applies if notification type is "Critical". default: "07:00:00" selector: time: {} fault_critical_end: name: Fault critical window end description: > Time after which fault notifications stop being critical. Only applies if notification type is "Critical". Set both start and end to the same time for critical ALL DAY. default: "21:00:00" selector: time: {} # ══════════════════════════════════════════════════════════════════════════ # CRITICAL ALERT SETTINGS # ══════════════════════════════════════════════════════════════════════════ critical_alert_settings: name: Critical Alert Settings icon: mdi:volume-high description: Sound and volume for critical notifications (iOS) collapsed: true input: critical_sound: name: Critical alert sound description: > Sound name for critical alerts (iOS). Common options: default, alert, bell, chime, glass, horn, etc. default: "default" selector: text: critical_volume: name: Critical alert volume description: Volume for critical alerts (0.0 to 1.0). default: 1.0 selector: number: min: 0.0 max: 1.0 step: 0.1 mode: slider # ══════════════════════════════════════════════════════════════════════════ # CUSTOM ACTIONS # ══════════════════════════════════════════════════════════════════════════ custom_actions: name: Custom Actions icon: mdi:cog-play description: > Run custom actions when print completes or fails. Examples: turn on a light, activate a switch, run a script, send additional notifications. collapsed: true input: success_actions: name: Actions on successful print (optional) description: > Actions to run when a print completes successfully. Examples: - Turn on a green "print done" indicator light - Activate a smart plug to power on a fan for cooling - Run a script to announce completion via TTS - Turn off the printer after a delay Leave empty to skip. default: [] selector: action: {} fault_actions: name: Actions on print fault (optional) description: > Actions to run when a print fails or encounters an error. Examples: - Turn on a red warning light - Flash lights to get attention - Pause other printers in a farm - Send alerts to additional services Leave empty to skip. default: [] selector: action: {} # ══════════════════════════════════════════════════════════════════════════ # TIMING & THRESHOLDS # ══════════════════════════════════════════════════════════════════════════ timing_settings: name: Timing & Thresholds icon: mdi:timer-cog description: Configure cooldown and trigger thresholds collapsed: true input: cooldown_minutes: name: Cooldown between alerts (minutes) description: Minimum time between any alerts from this automation. default: 5 selector: number: min: 0 max: 120 unit_of_measurement: min mode: slider progress_trigger_threshold: name: Progress trigger threshold (%) description: > Trigger snapshot at this progress %. Recommended 98-99 to capture before bed drops. default: 99 selector: number: min: 95 max: 100 unit_of_measurement: "%" mode: slider # ══════════════════════════════════════════════════════════════════════════ # OPTIONAL FEATURES # ══════════════════════════════════════════════════════════════════════════ optional_features: name: Optional Features icon: mdi:puzzle description: Additional optional configuration collapsed: true input: printers_view_uri: name: Printers view URI (optional) description: > Lovelace path, e.g. /lovelace/3d_printers Leave blank to disable action button on notifications. default: "" selector: text: {} mode: queued max: 3 variables: # Sensor entities print_status_sensor: !input print_status_sensor print_error_binary: !input print_error_binary current_stage_sensor: !input current_stage_sensor progress_sensor: !input progress_sensor printer_name_sensor: !input printer_name_sensor task_name_sensor: !input task_name_sensor print_weight_sensor: !input print_weight_sensor camera_entity: !input camera notifications_enabled_boolean: !input notifications_enabled_boolean # Snapshot settings snapshot_light: !input snapshot_light snapshot_light_brightness: !input snapshot_light_brightness snapshot_delay_seconds: !input snapshot_delay_seconds # Notification settings notify_device: !input notify_device success_notification_type: !input success_notification_type success_critical_start: !input success_critical_start success_critical_end: !input success_critical_end fault_notification_type: !input fault_notification_type fault_critical_start: !input fault_critical_start fault_critical_end: !input fault_critical_end critical_sound: !input critical_sound critical_volume: !input critical_volume cooldown_minutes: !input cooldown_minutes progress_trigger_threshold: !input progress_trigger_threshold printers_view_uri: !input printers_view_uri # TTS settings tts_enable: !input tts_enable tts_service: !input tts_service tts_media_player: !input tts_media_player tts_volume: !input tts_volume tts_success_message: !input tts_success_message tts_fault_message: !input tts_fault_message tts_quiet_hours_enable: !input tts_quiet_hours_enable tts_quiet_hours_start: !input tts_quiet_hours_start tts_quiet_hours_end: !input tts_quiet_hours_end # Custom actions success_actions: !input success_actions fault_actions: !input fault_actions # Derived values printer_name: "{{ states(printer_name_sensor) }}" task_name: "{{ states(task_name_sensor) }}" print_weight: "{{ states(print_weight_sensor) }}" status: "{{ states(print_status_sensor) }}" progress: "{{ states(progress_sensor) | int(0) }}" current_stage: "{{ states(current_stage_sensor) }}" # Check if light is configured (handle both empty string and empty list) has_snapshot_light: > {{ snapshot_light is defined and snapshot_light not in ['', [], none] }} # Check if custom actions are configured has_success_actions: > {{ success_actions is defined and success_actions not in [[], none, ''] and success_actions | length > 0 }} has_fault_actions: > {{ fault_actions is defined and fault_actions not in [[], none, ''] and fault_actions | length > 0 }} # Check if TTS is configured has_tts: > {{ tts_enable and tts_media_player is defined and tts_media_player not in ['', [], none] }} # Check if currently in TTS quiet hours is_tts_quiet_hours: > {{ tts_quiet_hours_enable and strptime(tts_quiet_hours_start, '%H:%M:%S').time() != strptime(tts_quiet_hours_end, '%H:%M:%S').time() and ( (strptime(tts_quiet_hours_start, '%H:%M:%S').time() < strptime(tts_quiet_hours_end, '%H:%M:%S').time() and now().time() >= strptime(tts_quiet_hours_start, '%H:%M:%S').time() and now().time() < strptime(tts_quiet_hours_end, '%H:%M:%S').time()) or (strptime(tts_quiet_hours_start, '%H:%M:%S').time() >= strptime(tts_quiet_hours_end, '%H:%M:%S').time() and (now().time() >= strptime(tts_quiet_hours_start, '%H:%M:%S').time() or now().time() < strptime(tts_quiet_hours_end, '%H:%M:%S').time())) ) }} # Snapshot file paths snapshot_file: "/config/www/snapshots/bambu_{{ printer_name | lower | replace(' ', '_') }}.jpg" snapshot_url: "/local/snapshots/bambu_{{ printer_name | lower | replace(' ', '_') }}.jpg" trigger: - platform: homeassistant event: start id: startup - platform: event event_type: automation_reloaded id: reload # Primary trigger: progress crosses threshold - platform: numeric_state id: progress_threshold entity_id: !input progress_sensor above: !input progress_trigger_threshold # Backup trigger: print status changes to finish (only from running state) - platform: state id: status_finish entity_id: !input print_status_sensor from: "running" to: "finish" # Backup trigger: print status changes to failed (only from active states) - platform: state id: status_failed entity_id: !input print_status_sensor from: - "running" - "prepare" - "pause" to: "failed" # Backup trigger: error binary sensor (only from off state) - platform: state id: error_detected entity_id: !input print_error_binary from: "off" to: "on" condition: # Check notifications are enabled - condition: state entity_id: !input notifications_enabled_boolean state: "on" # Check sensors are available - condition: template value_template: > {{ not is_state(printer_name_sensor, 'unavailable') and not is_state(printer_name_sensor, 'unknown') and not is_state(print_status_sensor, 'unavailable') and not is_state(print_status_sensor, 'unknown') }} # Check cooldown period - condition: template value_template: > {% set last = state_attr('automation.' ~ this.object_id, 'last_triggered') %} {% if last == none %} true {% else %} {% set last_ts = as_timestamp(last) %} {% set diff = as_timestamp(now()) - last_ts %} {{ diff > 60 * (cooldown_minutes | int) }} {% endif %} # Check print is actually in progress (not just starting) - condition: template value_template: > {{ progress > 0 or current_stage in [ 'printing', 'inspecting_first_layer', 'auto_bed_leveling', 'paused' ] }} # Guard against spurious triggers when printer comes back online # If triggered by state change, ensure previous state wasn't offline/unavailable - condition: template value_template: > {% if trigger.from_state is defined and trigger.from_state.state is defined %} {{ trigger.from_state.state not in ['offline', 'unavailable', 'unknown'] }} {% else %} {{ true }} {% endif %} action: # ════════════════════════════════════════════════════════════════════════════ # SKIP ON STARTUP/RELOAD # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ trigger.id in ['startup', 'reload'] }}" then: - stop: "Automation reloaded, will trigger at next print completion" # ════════════════════════════════════════════════════════════════════════════ # SNAPSHOT WITH OPTIONAL LIGHT # Takes snapshot immediately (at 99% the bed is still up, or on finish/fault) # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ has_snapshot_light }}" then: # Save current light state - variables: light_was_on: "{{ is_state(snapshot_light, 'on') }}" light_previous_brightness: > {{ state_attr(snapshot_light, 'brightness') | int(255) if is_state(snapshot_light, 'on') else 255 }} # Turn on light at specified brightness - service: light.turn_on target: entity_id: "{{ snapshot_light }}" data: brightness_pct: "{{ snapshot_light_brightness | int }}" # Wait for light to warm up and camera to adjust - delay: seconds: "{{ [snapshot_delay_seconds | int, 0] | max }}" # Take snapshot - service: camera.snapshot target: entity_id: "{{ camera_entity }}" data: filename: "{{ snapshot_file }}" continue_on_error: true # Restore light to previous state - if: - condition: template value_template: "{{ not light_was_on }}" then: - service: light.turn_off target: entity_id: "{{ snapshot_light }}" else: - service: light.turn_on target: entity_id: "{{ snapshot_light }}" data: brightness: "{{ light_previous_brightness }}" else: # No light configured - just delay (if any) and take snapshot - if: - condition: template value_template: "{{ (snapshot_delay_seconds | int) > 0 }}" then: - delay: seconds: "{{ snapshot_delay_seconds | int }}" - service: camera.snapshot target: entity_id: "{{ camera_entity }}" data: filename: "{{ snapshot_file }}" continue_on_error: true # ════════════════════════════════════════════════════════════════════════════ # WAIT FOR FINISH/FAULT STATE (only if triggered by progress threshold) # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ trigger.id == 'progress_threshold' }}" then: - wait_for_trigger: - platform: state entity_id: !input print_status_sensor to: - "finish" - "failed" - platform: state entity_id: !input print_error_binary to: "on" timeout: minutes: 10 continue_on_timeout: false # Small delay for state to settle - delay: "00:00:02" # ════════════════════════════════════════════════════════════════════════════ # CALCULATE NOTIFICATION PROPERTIES # ════════════════════════════════════════════════════════════════════════════ - variables: is_fault: > {{ is_state(print_status_sensor, 'failed') or is_state(print_error_binary, 'on') }} # Determine if we should use critical alert # Logic: # - If notification_type is not 'critical', always false # - If notification_type is 'critical': # - If start == end (including 00:00 to 00:00), critical ALL DAY # - Otherwise, only critical within the time window use_critical_alert: > {% set notification_type = fault_notification_type if is_fault else success_notification_type %} {% if notification_type != 'critical' %} {{ false }} {% else %} {% set start_str = fault_critical_start if is_fault else success_critical_start %} {% set end_str = fault_critical_end if is_fault else success_critical_end %} {% set start = strptime(start_str, '%H:%M:%S').time() %} {% set end = strptime(end_str, '%H:%M:%S').time() %} {% set now_time = now().time() %} {% if start == end %} {{ true }} {% elif start < end %} {{ now_time >= start and now_time <= end }} {% else %} {{ now_time >= start or now_time <= end }} {% endif %} {% endif %} notification_title: > {% if is_fault %} {{ printer_name }} FAULT {% else %} {{ printer_name }} Print Complete {% endif %} notification_message: > {% if is_fault %} ❌ Print Failed File: {{ task_name or 'Unknown' }} Progress: {{ progress }}% Status: {{ status }} {% else %} ✅ Print Successful File: {{ task_name or 'Unknown' }} Weight: {{ print_weight }}g Progress: {{ progress }}% {% endif %} # Render TTS messages with variables tts_message: > {% if is_fault %} {{ tts_fault_message }} {% else %} {{ tts_success_message }} {% endif %} # ════════════════════════════════════════════════════════════════════════════ # SEND MOBILE NOTIFICATION # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ notify_device != '' }}" then: - choose: # SUCCESS NOTIFICATION - conditions: - condition: template value_template: "{{ not is_fault }}" sequence: - service: "notify.{{ notify_device }}" data: title: "{{ notification_title }}" message: "{{ notification_message }}" data: image: "{{ snapshot_url }}" tag: "bambu_print_{{ printer_name }}" sticky: false persistent: false push: > {% if use_critical_alert %} {{ {'sound': {'name': critical_sound, 'critical': 1, 'volume': critical_volume}} }} {% else %} {} {% endif %} actions: > {% if printers_view_uri != '' %} [{"action": "URI", "title": "Open Printers", "uri": "{{ printers_view_uri }}"}] {% else %} [] {% endif %} # FAULT NOTIFICATION - conditions: - condition: template value_template: "{{ is_fault }}" sequence: - service: "notify.{{ notify_device }}" data: title: "{{ notification_title }}" message: "{{ notification_message }}" data: image: "{{ snapshot_url }}" tag: "bambu_print_{{ printer_name }}" sticky: true persistent: true push: > {% if use_critical_alert %} {{ {'sound': {'name': critical_sound, 'critical': 1, 'volume': critical_volume}} }} {% else %} {} {% endif %} actions: > {% if printers_view_uri != '' %} [{"action": "URI", "title": "Open Printers", "uri": "{{ printers_view_uri }}"}] {% else %} [] {% endif %} # ════════════════════════════════════════════════════════════════════════════ # PERSISTENT NOTIFICATION FOR FAULTS # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ is_fault }}" then: - service: persistent_notification.create data: title: "{{ printer_name }} Fault" message: > {{ task_name or 'Print job' }} on {{ printer_name }} has a fault. Status: {{ status }}, progress {{ progress }}%. notification_id: "bambu_fault_{{ printer_name }}" # ════════════════════════════════════════════════════════════════════════════ # TTS ANNOUNCEMENT # ════════════════════════════════════════════════════════════════════════════ - if: - condition: template value_template: "{{ has_tts and not is_tts_quiet_hours }}" then: # Set volume if specified - if: - condition: template value_template: "{{ tts_volume > 0 }}" then: - service: media_player.volume_set target: entity_id: "{{ tts_media_player }}" data: volume_level: "{{ tts_volume / 100 }}" # Send TTS announcement - choose: # Use tts.speak for modern HA (2023.1+) - conditions: - condition: template value_template: "{{ tts_service == 'tts.speak' }}" sequence: - service: tts.speak target: entity_id: "{{ tts_service.split('.')[0] ~ '.' ~ tts_service.split('.')[1] if '.' in tts_service else 'tts.google_translate_say' }}" data: media_player_entity_id: "{{ tts_media_player }}" message: "{{ tts_message }}" continue_on_error: true # Fallback: Use the specified TTS service directly default: - service: "{{ tts_service }}" data: entity_id: "{{ tts_media_player }}" message: "{{ tts_message }}" continue_on_error: true # ════════════════════════════════════════════════════════════════════════════ # RUN CUSTOM ACTIONS # ════════════════════════════════════════════════════════════════════════════ - choose: # SUCCESS ACTIONS - conditions: - condition: template value_template: "{{ not is_fault and has_success_actions }}" sequence: !input success_actions # FAULT ACTIONS - conditions: - condition: template value_template: "{{ is_fault and has_fault_actions }}" sequence: !input fault_actions