Skip to content

Instantly share code, notes, and snippets.

@Nasawa
Last active December 12, 2025 23:10
Show Gist options
  • Select an option

  • Save Nasawa/3de5049337cacb75e31296eea2e13d4b to your computer and use it in GitHub Desktop.

Select an option

Save Nasawa/3de5049337cacb75e31296eea2e13d4b to your computer and use it in GitHub Desktop.
Home Assistant Blueprint for monitoring a 3D printer using an AI Task
# ============================================================================
# AI 3D Printer Status Check Blueprint
# ============================================================================
# Purpose: Monitors camera/image entities and uses AI to detect print failures
# Author: Anigeek
# Version: 2.0.0
# ============================================================================
blueprint:
name: AI 3D Printer Status Check 2.0
description: >
Monitors a camera or image entity on a repeating cadence **while** a state sensor
equals a target value (case-insensitive).
Each cycle runs a Home Assistant **AI Task** on a frame and exposes the AI result
+ snapshot path to an optional user script for platform-specific notifications.
• **Local AI** = attach **camera** directly to the AI Task (fast, high quality).
• **Remote AI** = attach **snapshot** file via Local Media (requires allowlisted media path).
• **Image entities** = analyzed via entity_picture URL.
• Optional confidence threshold (fixed number or entity) gates the built-in text alert.
• Built-in **debug** and **failure** messages are text-only; image sending is delegated to your script.
• Snapshot naming: overwrite a single file or write timestamped files (no automatic cleanup).
• **Test mode** = run a single check immediately for testing configuration.
**Notes:**
- Image sending is delegated to **script_on_result**, keeping this blueprint notify-platform agnostic.
- The script receives **nested objects** for clarity and long-term stability (see Scripts section).
Community: [Community link for this blueprint](community.home-assistant.io/t/3d-printer-ai-status-check/919182)
Version: 2.0.0
domain: automation
source_url: https://gist.github.com/Nasawa/3de5049337cacb75e31296eea2e13d4b#file-3d-print-ai-health-check-yaml
# --------------------------------------------------------------------------
# INPUT CONFIGURATION
# --------------------------------------------------------------------------
# All user-configurable parameters are defined here
# --------------------------------------------------------------------------
input:
# === CORE INPUTS ===
# Required settings for basic operation
core:
name: Core inputs
icon: mdi:camera
collapsed: false
description: Required entities and cadence.
input:
image_entity:
name: Image or Camera Entity
description: >
Camera or image entity to monitor. Camera entities save snapshots to the configured path.
Image entities are analyzed directly from their current state.
selector:
entity:
domain:
- camera
- image
multiple: false
stage_sensor:
name: State sensor
description: Entity whose state controls when monitoring is active
selector:
entity:
domain:
- sensor
multiple: false
active_state:
name: Active state value
description: Case-insensitive match that enables monitoring (e.g., "Printing")
default: Printing
selector:
text:
multiline: false
interval_minutes:
name: Interval (minutes)
default: 15
selector:
number:
min: 1.0
max: 120.0
step: 1.0
mode: slider
test_mode:
name: Test mode (run once immediately)
description: >
When enabled, runs a single check immediately regardless of state sensor value.
Useful for testing configuration. Automation will complete after one cycle.
default: false
selector:
boolean: {}
# === AI SETTINGS ===
# Configure AI behavior, prompts, and image handling
ai_settings:
name: AI settings
icon: mdi:robot
collapsed: false
description: Configure AI analysis behavior, task entity, and image attachment method.
input:
ai_task_entity:
name: AI Task entity (optional)
description: If set, this AI Task will be used instead of the system default.
default: ''
selector:
entity:
domain:
- ai_task
multiple: false
ai_task_name:
name: AI Task Name
description: Name for the AI task (useful for organizing multiple automations)
default: 'Image Health Check'
selector:
text:
multiline: false
ai_instructions:
name: AI Instructions
description: Custom prompt for the AI to analyze the image
default: >
You are inspecting a 3D printer's camera frame. Determine if the print is going wrong.
Common failures include spaghetti (stringy filament), poor first-layer adhesion,
severe warping/corner lift, nozzle clog with under-extrusion, layer shift,
knocked-over part, filament blob near the hotend, or the part detaching from the bed.
If there is meaningful risk of failure without intervention, set has_problem to true
and provide brief, actionable advice.
selector:
text:
multiline: true
ai_schema:
name: AI Response Schema (JSON)
description: Define the structure of the AI response. Must be valid JSON.
default: |
{
"has_problem": {"selector": {"boolean": {}}, "required": true},
"problem_type": {"selector": {"text": {}}},
"confidence": {"selector": {"number": {}}, "description": "0-100 (fraction like 0.85 will be interpreted as 85%)."},
"advice": {"selector": {"text": {}}}
}
selector:
text:
multiline: true
use_local_model:
name: Use local AI (attach camera directly)
description: >
ON: attach the camera live frame via media-source (good for local backends like Ollama).
OFF: attach a snapshot file via Local Media (recommended for remote providers).
Note: Image entities always use snapshot mode.
default: true
selector:
boolean: {}
snapshot_path_base:
name: Local Media filesystem path
description: >
Filesystem path for your Local Media folder (NOT a URL). Commonly **/media**,
sometimes **/config/media**. Must be listed under **homeassistant.allowlist_external_dirs**.
default: /media
selector:
text:
multiline: false
snapshot_subfolder:
name: Subfolder (under Local Media)
description: >
Optional subfolder name. If blank, defaults to `ai_health/<entity_object_id>` to avoid collisions.
Ensure this folder exists before running the automation.
default: ''
selector:
text:
multiline: false
image_format:
name: Image Format
description: Image format for saved snapshots
default: jpg
selector:
select:
options:
- label: JPEG
value: jpg
- label: PNG
value: png
- label: WebP
value: webp
# === THRESHOLDS ===
# Optional confidence-based filtering
thresholds:
name: Thresholds (optional)
icon: mdi:tune-variant
collapsed: true
description: >
Gate the built-in text alert by minimum AI confidence. Your result script still receives all results.
input:
confidence_threshold:
name: Confidence threshold (fixed)
description: Notify only when normalized confidence ≥ this value (0-100). Leave blank to disable.
selector:
number:
min: 0.0
max: 100.0
step: 1.0
mode: slider
confidence_threshold_entity:
name: Confidence threshold entity (number or sensor)
description: If set, this entity's numeric value (0-100) takes precedence over the fixed value.
default: ''
selector:
entity:
domain:
- number
- sensor
multiple: false
# === SCRIPTS ===
# Optional custom script for handling results
scripts:
name: Script (optional)
icon: mdi:script-text-outline
collapsed: true
description: |
Provide a script to handle platform-specific notifications or archival.
The same script is called for success **and** failure; on failure, an `error` object is included.
<details>
<summary><strong>script_on_result payload (nested objects)</strong></summary>
```yaml
# On success:
ai_result:
has_problem: bool
problem_type: string|null
advice: string|null
confidence:
raw: number|null # as returned by the model (0.85 or 85)
normalized: number|null # 0-100; null if model omitted confidence
snapshot:
abs_path: string # absolute file path
rel_path: string # relative under Local Media (e.g., subfolder/filename.jpg)
provider_mode: string # "local" or "remote"
script_context:
image_entity: string
ai_task_entity: string|null
ts: string # ISO8601 timestamp
cycle_number: number # iteration count for this automation run
threshold:
active: boolean
value: number|null
notified: boolean # whether built-in alert fired this cycle
error:
message: string # not provided if there is no error
```
</details>
input:
script_on_result:
name: Script on result (success or failure)
description: Optional script called each cycle; receives nested objects as above.
default: ''
selector:
entity:
domain:
- script
multiple: false
# === HOUSEKEEPING ===
# File management options
housekeeping:
name: Housekeeping (optional)
icon: mdi:broom
collapsed: true
description: >
Control snapshot file naming. This blueprint does not delete files.
If you choose timestamped files, you'll need to handle cleanup yourself.
input:
overwrite_snapshot:
name: Overwrite single file (latest)
description: >
If ON, writes `<subfolder>/latest.<ext>` each run.
OFF writes timestamped files (no auto-cleanup).
default: true
selector:
boolean: {}
# === NOTIFICATIONS ===
# Built-in notification configuration
notifications:
name: Built-in notifications (optional)
icon: mdi:message-outline
collapsed: true
description: >
Text-only status messages from the blueprint itself.
Custom notify actions have access to all variables including ai, normalized_confidence,
snapshot paths, has_problem, etc.
input:
notify_action:
name: Custom notify action (optional)
description: >
An action (or sequence) to run when the blueprint emits a text notification.
If provided, it will be used instead of the simple notify service below.
Available variables: notification_title, notification_message, notification_type,
ai (full response), normalized_confidence, raw_confidence, has_problem,
problem_type, advice, snapshot_abs_path, snapshot_rel_path, cycle_number, and more.
default: []
selector:
action: {}
notify_service:
name: Notify service for text pings (fallback)
description: >
e.g., `notify.mobile_app_<device>` or `notify.persistent_notification`.
Leave blank to disable if not using a custom action.
default: ''
selector:
text:
multiline: false
# === DEBUGGING ===
# Debug mode configuration
debugging:
name: Debug (optional)
icon: mdi:bug
collapsed: true
description: Extra telemetry each cycle.
input:
debug_mode:
name: Debug mode
default: false
selector:
boolean: {}
# ----------------------------------------------------------------------------
# TRIGGER CONFIGURATION
# ----------------------------------------------------------------------------
# Automation triggers on state sensor changes or Home Assistant start
# ----------------------------------------------------------------------------
triggers:
# Trigger when the stage sensor changes state
- trigger: state
entity_id: !input stage_sensor
# Trigger when Home Assistant starts (to resume monitoring)
- trigger: homeassistant
event: start
# Restart mode: if triggered again, cancel current run and start fresh
mode: restart
# ----------------------------------------------------------------------------
# VARIABLE DEFINITIONS
# ----------------------------------------------------------------------------
# All variables are computed once at automation start and remain constant
# throughout the automation run (except cycle_count which increments)
# ----------------------------------------------------------------------------
variables:
# ===== INPUT REFERENCES =====
# Store input values in variables for easier access
image_entity: !input image_entity
stage_sensor: !input stage_sensor
interval_min: !input interval_minutes
active_state: !input active_state
test_mode_enabled: !input test_mode
ai_entity: !input ai_task_entity
ai_task_name: !input ai_task_name
ai_instructions: !input ai_instructions
ai_schema_raw: !input ai_schema
use_local: !input use_local_model
snapshot_base: !input snapshot_path_base
snapshot_subfolder: !input snapshot_subfolder
image_format: !input image_format
threshold_fixed: !input confidence_threshold
threshold_entity: !input confidence_threshold_entity
script_result: !input script_on_result
overwrite_snapshot: !input overwrite_snapshot
notify_action: !input notify_action
notify_service: !input notify_service
debug_enabled: !input debug_mode
# ===== CONFIGURATION FLAGS =====
# Boolean flags to check if optional features are configured
# Check if custom notify action is configured (array has items)
has_notify_action: '{{ (notify_action | default([])) | length > 0 }}'
# Check if notify service is configured (non-empty string)
has_notify_service: '{{ (notify_service | string | trim) | length > 0 }}'
# Check if custom AI task entity is specified
has_ai_entity: '{{ (ai_entity | string | trim) | length > 0 }}'
# Check if result script is configured
has_script: '{{ (script_result | string | trim) | length > 0 }}'
# ===== JSON SCHEMA VALIDATION =====
# Validate the AI schema is valid JSON before attempting to use it
# Test if schema can be parsed as JSON (returns none if invalid)
ai_schema_valid: >
{% set test_parse = ai_schema_raw | from_json(none) %}
{{ test_parse is not none }}
# Parse schema if valid, otherwise set to none (will trigger error later)
ai_schema: >
{% if ai_schema_valid %}
{{ ai_schema_raw | from_json }}
{% else %}
{{ none }}
{% endif %}
# ===== THRESHOLD CALCULATION =====
# Determine which threshold to use (entity takes precedence over fixed)
# Check if threshold entity is provided
threshold_entity_provided: '{{ (threshold_entity | string | trim) | length > 0 }}'
# Get threshold value from entity (if provided)
threshold_from_entity: '{{ states(threshold_entity) | float(''nan'') if threshold_entity_provided else float(''nan'') }}'
# Get threshold value from fixed input (if provided)
threshold_from_fixed: '{{ threshold_fixed | float(''nan'') if (threshold_fixed is not none) else float(''nan'') }}'
# Final threshold value: entity takes precedence over fixed
confidence_threshold: >
{% if threshold_entity_provided %}
{{ threshold_from_entity }}
{% else %}
{{ threshold_from_fixed }}
{% endif %}
# Check if threshold is active (value == value means not NaN)
threshold_active: '{{ confidence_threshold == confidence_threshold }}'
# Check if both threshold methods are configured (for warning)
both_thresholds_set: '{{ threshold_entity_provided and (threshold_fixed is not none) }}'
# ===== ENTITY INFORMATION =====
# Extract and compute entity-related information
# Get entity object ID (e.g., "camera.printer" -> "printer")
entity_obj_id: '{{ (image_entity | string).split(''.'')[-1] }}'
# Get entity domain (e.g., "camera.printer" -> "camera")
entity_domain: '{{ (image_entity | string).split(''.'')[0] }}'
# Check if entity is a camera
is_camera: '{{ entity_domain == ''camera'' }}'
# Check if entity is an image entity
is_image: '{{ entity_domain == ''image'' }}'
# ===== IMAGE FORMAT MAPPING =====
# Map user-selected format to file extension and MIME type
# File extension based on selected format
format_extension: >
{% if image_format == 'jpg' %}jpg
{% elif image_format == 'png' %}png
{% elif image_format == 'webp' %}webp
{% else %}jpg{% endif %}
# MIME type for AI task attachment
format_mime_type: >
{% if image_format == 'jpg' %}image/jpeg
{% elif image_format == 'png' %}image/png
{% elif image_format == 'webp' %}image/webp
{% else %}image/jpeg{% endif %}
# ===== SNAPSHOT PATH CONSTRUCTION =====
# Build file paths for saving snapshots
# Default subfolder if user doesn't specify one
default_subfolder: 'ai_health/{{ entity_obj_id }}'
# Final subfolder (use user-specified or default)
folder: >
{% set sf = (snapshot_subfolder | string | trim) %}
{% if sf | length > 0 %}
{{ sf.strip('/') }}
{% else %}
{{ default_subfolder }}
{% endif %}
# Absolute path to the snapshot folder
folder_abs_path: '{{ snapshot_base.rstrip(''/'') }}/{{ folder }}'
# ============================================================================
# ACTION SEQUENCE
# ============================================================================
# The main automation logic - runs in order from top to bottom
# ============================================================================
actions:
# --------------------------------------------------------------------------
# STEP 1: VALIDATE CONFIGURATION
# --------------------------------------------------------------------------
# Ensure all configuration is valid before proceeding
# --------------------------------------------------------------------------
# === VALIDATE JSON SCHEMA ===
# If the AI schema is invalid JSON, notify user and stop
- choose:
- conditions:
- condition: template
value_template: '{{ not ai_schema_valid }}'
sequence:
# Set notification variables for the error
- variables:
notification_title: 'AI Health Check - Configuration Error'
notification_message: >
The AI Response Schema is not valid JSON. Please fix the schema in the blueprint configuration.
Automation cannot continue.
notification_type: 'error'
# Send notification using configured method
- choose:
# Use custom notify action if configured
- conditions:
- condition: template
value_template: '{{ has_notify_action }}'
sequence: !input notify_action
# Fallback to notify service if configured
- conditions:
- condition: template
value_template: '{{ has_notify_service }}'
sequence:
- action: !input notify_service
data:
title: '{{ notification_title }}'
message: '{{ notification_message }}'
# Stop the automation - cannot proceed with invalid schema
- stop: 'Invalid AI schema - cannot continue'
# === WARN ABOUT THRESHOLD CONFLICT ===
# If both fixed and entity thresholds are set, warn the user
- choose:
- conditions:
- condition: template
value_template: '{{ both_thresholds_set }}'
sequence:
- action: persistent_notification.create
data:
title: AI Health Check - Threshold Conflict
message: >
Both a fixed confidence threshold and a threshold entity were provided.
The entity value will be used. Clear one of them to remove this message.
# === SNAPSHOT DIRECTORY ===
# Note: The snapshot directory should exist before running this automation.
# Most camera.snapshot and downloader services will create parent directories
# automatically, but if you encounter errors, manually create the folder first.
# --------------------------------------------------------------------------
# STEP 2: CHECK RUN CONDITIONS
# --------------------------------------------------------------------------
# Only proceed if test mode is enabled OR stage sensor matches active state
# --------------------------------------------------------------------------
- condition: template
value_template: '{{ test_mode_enabled or ((states(stage_sensor) | lower) == (active_state | lower)) }}'
# === RANDOM DELAY ===
# Add 0-5 second random delay to avoid simultaneous triggers
# (skipped in test mode for immediate execution)
- choose:
- conditions:
- condition: template
value_template: '{{ not test_mode_enabled }}'
sequence:
- delay:
seconds: '{{ range(0, 5) | random }}'
# --------------------------------------------------------------------------
# STEP 3: INITIALIZE CYCLE TRACKING
# --------------------------------------------------------------------------
# Set up cycle counter for tracking iterations
# --------------------------------------------------------------------------
- variables:
cycle_count: 0
# --------------------------------------------------------------------------
# STEP 4: MAIN MONITORING LOOP
# --------------------------------------------------------------------------
# Repeatedly check the image until conditions change
# In test mode, runs exactly once
# --------------------------------------------------------------------------
- repeat:
# Exit condition: test mode OR stage sensor no longer matches active state
until:
- condition: template
value_template: '{{ test_mode_enabled or ((states(stage_sensor) | lower) != (active_state | lower)) }}'
sequence:
# ======================================================================
# CYCLE START - INCREMENT COUNTER AND GENERATE PATHS
# ======================================================================
# === INCREMENT CYCLE COUNTER ===
# Track which iteration we're on
- variables:
cycle_count: '{{ cycle_count + 1 }}'
# Generate timestamp for this cycle (format: YYYYMMDD-HHMMSS)
cycle_ts: '{{ now().strftime(''%Y%m%d-%H%M%S'') }}'
# Determine filename: either "latest.ext" or "timestamp.ext"
filename: '{% if overwrite_snapshot %}latest.{{ format_extension }}{% else %}{{ cycle_ts }}.{{ format_extension }}{% endif %}'
# Build relative path (under Local Media)
snapshot_rel_path: '{{ folder }}/{{ filename }}'
# Build absolute filesystem path
snapshot_abs_path: '{{ folder_abs_path }}/{{ filename }}'
# Build media source ID for Home Assistant media browser
media_source_id: 'media-source://media_source/local/{{ snapshot_rel_path }}'
# ======================================================================
# IMAGE CAPTURE - CAMERA SNAPSHOT
# ======================================================================
# Capture snapshot for camera entities
# Image entities are analyzed directly from entity_picture URL (no snapshot needed)
- choose:
- conditions:
- condition: template
value_template: '{{ is_camera }}'
sequence:
- action: camera.snapshot
continue_on_error: true
target:
entity_id: !input image_entity
data:
filename: '{{ snapshot_abs_path }}'
# Short delay to ensure snapshot is fully written
- delay: 00:00:02
# ======================================================================
# AI TASK PREPARATION - BUILD REQUEST DATA
# ======================================================================
# Determine how to attach the image to the AI task
- variables:
# Use direct camera feed if: local mode + camera entity
use_direct_camera: '{{ use_local and is_camera }}'
# Build attachment URL based on entity type and mode
ai_attachment: >
{% if use_direct_camera %}
media-source://camera/{{ image_entity }}
{% elif is_image %}
{{ state_attr(image_entity, 'entity_picture') }}
{% else %}
{{ media_source_id }}
{% endif %}
# Build full task name with mode indicator
ai_task_name_full: '{{ ai_task_name }} ({{ ''live'' if use_direct_camera else ''snapshot'' }})'
# ======================================================================
# AI TASK EXECUTION - CALL AI TO ANALYZE IMAGE
# ======================================================================
# Call either custom AI task entity or system default
- choose:
# === CUSTOM AI TASK ENTITY ===
# User specified a particular AI task to use
- conditions:
- condition: template
value_template: '{{ has_ai_entity }}'
sequence:
- action: ai_task.generate_data
continue_on_error: true # Don't stop automation if AI fails
data:
entity_id: !input ai_task_entity
task_name: '{{ ai_task_name_full }}'
instructions: '{{ ai_instructions }}'
structure: '{{ ai_schema }}'
attachments:
media_content_id: '{{ ai_attachment }}'
media_content_type: '{{ format_mime_type }}'
response_variable: ai # Store response in 'ai' variable
# === DEFAULT AI TASK ===
# Use Home Assistant's default/system AI task
default:
- action: ai_task.generate_data
continue_on_error: true
data:
task_name: '{{ ai_task_name_full }}'
instructions: '{{ ai_instructions }}'
structure: '{{ ai_schema }}'
attachments:
media_content_id: '{{ ai_attachment }}'
media_content_type: '{{ format_mime_type }}'
response_variable: ai
# ======================================================================
# ERROR HANDLING - HANDLE AI TASK FAILURES
# ======================================================================
# Check if AI task failed (response variable not defined)
- choose:
- conditions:
- condition: template
value_template: '{{ ai is not defined }}'
sequence:
# === CALL USER SCRIPT WITH ERROR ===
# Let user's script know the AI task failed
- choose:
- conditions:
- condition: template
value_template: '{{ has_script }}'
sequence:
- action: !input script_on_result
data:
error:
message: no assistant content returned
snapshot:
abs_path: '{{ snapshot_abs_path }}'
rel_path: '{{ snapshot_rel_path }}'
provider_mode: '{{ ''local'' if use_direct_camera else ''remote'' }}'
script_context:
image_entity: '{{ image_entity }}'
ai_task_entity: '{{ ai_entity if has_ai_entity else none }}'
ts: '{{ now().isoformat() }}'
cycle_number: '{{ cycle_count }}'
# === SEND FAILURE NOTIFICATION ===
# Notify user about the AI task failure
- variables:
notification_title: 'AI Health Check - Processing Failed'
notification_message: >
The AI task did not return a valid assistant message this cycle.
Try a different instruct/JSON-friendly model. (Monitoring continues.)
Cycle: {{ cycle_count }}
notification_type: 'error'
# Expose variables for custom notify action
snapshot_abs_path: '{{ snapshot_abs_path }}'
snapshot_rel_path: '{{ snapshot_rel_path }}'
cycle_number: '{{ cycle_count }}'
# Send notification via configured method
- choose:
- conditions:
- condition: template
value_template: '{{ has_notify_action }}'
sequence: !input notify_action
- conditions:
- condition: template
value_template: '{{ has_notify_service }}'
sequence:
- action: !input notify_service
data:
title: '{{ notification_title }}'
message: '{{ notification_message }}'
# ======================================================================
# RESULT PROCESSING - EXTRACT AND NORMALIZE AI RESULTS
# ======================================================================
# Extract and normalize confidence, problem status, etc.
- variables:
# === EXTRACT RAW CONFIDENCE ===
# AI may return confidence in different formats
raw_confidence: >
{% if ai is defined and (ai.data.confidence is defined or ai.data.get('confidence') is not none) %}
{{ ai.data.confidence if (ai.data.confidence is defined) else ai.data.get('confidence') }}
{% else %}
{{ none }}
{% endif %}
# === NORMALIZE CONFIDENCE ===
# Convert to 0-100 scale (AI might return 0.85 or 85)
normalized_confidence: >
{% if raw_confidence is not none %}
{% set c = raw_confidence | float(0) %}
{{ (c * 100) if c <= 1 else c }}
{% else %}
{{ none }}
{% endif %}
# === EXTRACT PROBLEM STATUS ===
# Check if AI detected a problem
has_problem: '{{ ai is defined and (ai.data.has_problem | default(false)) }}'
# === EXTRACT PROBLEM DETAILS ===
# Get problem type and advice if available
problem_type: '{{ ai.data.problem_type | default(none) if ai is defined else none }}'
advice: '{{ ai.data.advice | default(none) if ai is defined else none }}'
# === CHECK THRESHOLD ===
# Determine if confidence meets threshold (if active)
meets_threshold: >
{% if not threshold_active %}
true
{% else %}
{% if normalized_confidence is none %}
false
{% else %}
{{ (normalized_confidence | float(0)) >= (confidence_threshold | float(0)) }}
{% endif %}
{% endif %}
# === DETERMINE IF WE SHOULD NOTIFY ===
# Only notify if: problem detected AND threshold met
should_notify: '{{ has_problem and meets_threshold }}'
# === EXPOSE PATHS FOR NOTIFICATIONS ===
# Make snapshot paths available to custom notify actions
snapshot_abs_path: '{{ snapshot_abs_path }}'
snapshot_rel_path: '{{ snapshot_rel_path }}'
cycle_number: '{{ cycle_count }}'
# ======================================================================
# PROBLEM NOTIFICATION - ALERT IF ISSUE DETECTED
# ======================================================================
# Send alert if problem detected and threshold met
- choose:
- conditions:
- condition: template
value_template: '{{ should_notify }}'
sequence:
# Build notification message with problem details
- variables:
notification_title: '⚠️ Image Issue Detected'
notification_message: >
Type: {{ problem_type | default('unknown') }}
Confidence: {{ (normalized_confidence | float(0)) | round(1) if (normalized_confidence is not none) else '—' }}%
Advice: {{ advice | default('Check the print ASAP.') }}
Cycle: {{ cycle_count }}
(via AI Task)
notification_type: 'problem'
# Send via configured notification method
- choose:
- conditions:
- condition: template
value_template: '{{ has_notify_action }}'
sequence: !input notify_action
- conditions:
- condition: template
value_template: '{{ has_notify_service }}'
sequence:
- action: !input notify_service
data:
title: '{{ notification_title }}'
message: '{{ notification_message }}'
# ======================================================================
# USER SCRIPT CALLBACK - PASS RESULTS TO CUSTOM SCRIPT
# ======================================================================
# Call user's result script with full AI results
- choose:
- conditions:
- condition: template
value_template: '{{ has_script and ai is defined }}'
sequence:
- action: !input script_on_result
data:
# AI analysis results
ai_result:
has_problem: '{{ has_problem }}'
problem_type: '{{ problem_type }}'
advice: '{{ advice }}'
confidence:
raw: '{{ raw_confidence if (raw_confidence is not none) else none }}'
normalized: '{{ normalized_confidence if (normalized_confidence is not none) else none }}'
# Snapshot information
snapshot:
abs_path: '{{ snapshot_abs_path }}'
rel_path: '{{ snapshot_rel_path }}'
provider_mode: '{{ ''local'' if use_direct_camera else ''remote'' }}'
# Context and metadata
script_context:
image_entity: '{{ image_entity }}'
ai_task_entity: '{{ ai_entity if has_ai_entity else none }}'
ts: '{{ now().isoformat() }}'
cycle_number: '{{ cycle_count }}'
threshold:
active: '{{ threshold_active }}'
value: '{{ confidence_threshold if threshold_active else none }}'
notified: '{{ should_notify }}'
# ======================================================================
# DEBUG OUTPUT - DETAILED TELEMETRY IF ENABLED
# ======================================================================
# Send detailed debug information if debug mode is on
- choose:
- conditions:
- condition: template
value_template: '{{ debug_enabled }}'
sequence:
# Build comprehensive debug message
- variables:
notification_title: 'AI Health Check - Debug'
notification_message: >
{% if ai is not defined %}
Cycle {{ cycle_count }}: AI failed this cycle (no assistant content).
{% else %}
Cycle {{ cycle_count }}:
Has Problem={{ has_problem }},
Type={{ problem_type | default('—') }},
Confidence (raw)={{ raw_confidence | default('—') }},
Confidence (normalized)={{ (normalized_confidence | float(0)) | round(2) if (normalized_confidence is not none) else '—' }},
Threshold Active={{ threshold_active }},
Threshold={{ (confidence_threshold | float(0)) | round(1) if threshold_active else '—' }},
Notified={{ should_notify }},
Snapshot={{ snapshot_abs_path }}
{% endif %}
notification_type: 'debug'
# Send debug notification
- choose:
- conditions:
- condition: template
value_template: '{{ has_notify_action }}'
sequence: !input notify_action
- conditions:
- condition: template
value_template: '{{ has_notify_service }}'
sequence:
- action: !input notify_service
data:
title: '{{ notification_title }}'
message: '{{ notification_message }}'
# ======================================================================
# CYCLE END - WAIT FOR NEXT INTERVAL
# ======================================================================
# Wait before next check (skipped in test mode for single run)
- choose:
- conditions:
- condition: template
value_template: '{{ not test_mode_enabled }}'
sequence:
- delay:
minutes: '{{ interval_min | int }}'
# ============================================================================
# END OF BLUEPRINT
# ============================================================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment