3D Printer AI Status Check

Hello! This is my first blueprint, so I hope I’m doing this right :sweat_smile:

This is a blueprint for sending a camera feed for a 3D printer to AI to try to catch problems and anomalies early. I’ve tested it as well as I reasonably can, but I’m sure there are edge cases I haven’t been able to take into account, so any feedback and suggestions are welcome!

I’ve used this with my Bambu P1S, and it seems pretty reliable, if a bit over-enthusiastic at times. I recommend tuning the threshold on the high side (I keep it around 85%) once you’re comfortable with it.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

Requirements

  • A camera entity
  • A way to know that the camera should be monitored (such as a sensor)
  • A configured AI Task entity
    • If the AI Task is remote (e.g., OpenAI, Gemini), you’ll need to ensure your local media path (such as /media) is allow-listed in homeassistant.allowlist_external_dirs. This is because remote AI Tasks cannot use media-source.
  • Home Assistant 2025.8+

Recommended

  • A script to handle notifications
    • Trying to handle every type of notification would be too complex, so the blueprint instead calls a script with the relevant variables that you can use to implement the notification provider of your choice.
    • If you are using a local AI Task configuration, you’ll still need to make sure that your media path is allowed if you want to send pictures with your notifications.

Code

blueprint:
  name: AI 3D Printer Status Check
  description: |
    Monitors a camera 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; see inputs).
    • 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).

    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 the Scripts section definitions).
  domain: automation

  input:
    core:
      name: Core inputs
      icon: mdi:camera
      collapsed: false
      description: >
        Required entities and cadence.
      input:
        camera_entity:
          name: Camera
          selector:
            entity:
              domain: camera
        stage_sensor:
          name: State sensor
          description: Entity whose state controls when monitoring is active
          selector:
            entity:
              domain: sensor
        active_state:
          name: Active state value
          description: Case‑insensitive match that enables monitoring (e.g., "Printing")
          default: "Printing"
          selector:
            text: {}
        interval_minutes:
          name: Interval (minutes)
          default: 15
          selector:
            number:
              min: 1
              max: 120
              step: 1
              mode: slider

    ai_settings:
      name: AI settings
      icon: mdi:robot
      collapsed: false
      description: Choose how to attach imagery and which AI Task to use. Remote AI requires Local Media access.
      input:
        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).
          default: true
          selector:
            boolean: {}
        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
        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** and the subfolder must exist.
          default: "/media"
          selector:
            text: {}
        snapshot_subfolder:
          name: Subfolder (under Local Media)
          description: >
            Optional subfolder name. If blank, defaults to `ai_task/<camera_object_id>` to avoid collisions.
          default: ""
          selector:
            text: {}

    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
              max: 100
              step: 1
        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

    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:
          camera_entity: string
          ai_task_entity: string|null
          ts: string                  # ISO8601 timestamp
          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

    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
        (e.g., via another automation/script or an external tool like a cron job).
      input:
        overwrite_snapshot:
          name: Overwrite single file (latest.jpg)
          description: If ON, writes `<subfolder>/latest.jpg` each run. OFF writes timestamped files (no auto-cleanup).
          default: true
          selector:
            boolean: {}

    notifications:
      name: Built‑in notifications (optional)
      icon: mdi:message-outline
      collapsed: true
      description: Text‑only status messages from the blueprint itself.
      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. This action can reference variables like `ai`,
            `norm_conf`, `abs_path`, `rel_path`, etc.
          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: {}

    debugging:
      name: Debug (optional)
      icon: mdi:bug
      collapsed: true
      description: Extra telemetry each cycle.
      input:
        debug_mode:
          name: Debug mode
          default: false
          selector:
            boolean: {}

# Triggers
triggers:
  - trigger: state
    entity_id: !input stage_sensor
  - trigger: homeassistant
    event: start

mode: restart

# Variables
variables:
  cam: !input camera_entity
  stage: !input stage_sensor
  interval: !input interval_minutes
  active_val: !input active_state

  use_local: !input local_model
  ai_entity: !input ai_task_entity
  snapshot_base: !input snapshot_path_base
  snapshot_subfolder: !input snapshot_subfolder

  thr_fixed: !input confidence_threshold
  thr_entity: !input confidence_threshold_entity

  script_result: !input script_on_result

  overwrite: !input overwrite_snapshot

  notify_act: !input notify_action
  notify_svc: !input notify_service
  debug_enabled: !input debug_mode

  has_notify_action: "{{ (notify_act | default([])) | length > 0 }}"
  has_notify_service: "{{ (notify_svc | string) | length > 0 }}"

  entity_provided: "{{ (thr_entity | string) | length > 0 }}"
  thr_from_entity: "{{ states(thr_entity) | float('nan') if entity_provided else float('nan') }}"
  thr_from_fixed: "{{ thr_fixed | float('nan') if (thr_fixed is not none) else float('nan') }}"
  conf_threshold: >
    {% if entity_provided %}{{ thr_from_entity }}{% else %}{{ thr_from_fixed }}{% endif %}
  threshold_active: "{{ conf_threshold == conf_threshold }}"
  both_thresholds_provided: "{{ entity_provided and (thr_fixed is not none) }}"

  # Resolve subfolder (default: ai_task/<camera object_id>) and filenames
  cam_obj: "{{ (cam | string).split('.')[-1] }}"
  folder: >
    {% set sf = (snapshot_subfolder | string) %}
    {% if sf|length > 0 %}{{ sf.strip('/') }}{% else %}ai_task/{{ cam_obj }}{% endif %}

  # Shared AI instructions & schema (generic wording)
  ai_instr: >-
    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.
  ai_schema: >-
    {
      "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": {}}}
    }

actions:
  # Warn once if both threshold inputs are filled — entity will be used.
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ both_thresholds_provided }}"
        sequence:
          - action: persistent_notification.create
            data:
              title: "AI image health — 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.

  # Only proceed if currently active (case‑insensitive)
  - condition: template
    value_template: "{{ (states(stage) | lower) == (active_val | lower) }}"

  # Jitter
  - delay:
      seconds: "{{ range(0, 5) | random }}"

  # Loop while active
  - repeat:
      until:
        - condition: template
          value_template: "{{ (states(stage) | lower) != (active_val | lower) }}"
      sequence:
        # Build paths and snapshot once per cycle (used for scripts & remote AI)
        - variables:
            ts: "{{ now().strftime('%Y%m%d-%H%M%S') }}"
            filename: >-
              {% if overwrite %}latest.jpg{% else %}{{ ts }}.jpg{% endif %}
            rel_path: "{{ folder }}/{{ filename }}"
            abs_path: "{{ snapshot_base.rstrip('/') ~ '/' ~ rel_path }}"
            media_source_id: "media-source://media_source/local/{{ rel_path }}"
        - action: camera.snapshot
          target:
            entity_id: !input camera_entity
          data:
            filename: "{{ abs_path }}"
        - delay: "00:00:02"

        # Run AI Task (local uses camera; remote uses snapshot file)
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ use_local | bool }}"
              sequence:
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ (ai_entity | string) | length > 0 }}"
                      sequence:
                        - action: ai_task.generate_data
                          continue_on_error: true
                          data:
                            entity_id: !input ai_task_entity
                            task_name: "image health check"
                            instructions: "{{ ai_instr }}"
                            structure: "{{ ai_schema | from_json }}"
                            attachments:
                              media_content_id: "media-source://camera/{{ cam }}"
                              media_content_type: image/jpeg
                          response_variable: ai
                    - conditions:
                        - condition: template
                          value_template: "{{ (ai_entity | string) | length == 0 }}"
                      sequence:
                        - action: ai_task.generate_data
                          continue_on_error: true
                          data:
                            task_name: "image health check"
                            instructions: "{{ ai_instr }}"
                            structure: "{{ ai_schema | from_json }}"
                            attachments:
                              media_content_id: "media-source://camera/{{ cam }}"
                              media_content_type: image/jpeg
                          response_variable: ai
            - conditions:
                - condition: template
                  value_template: "{{ not (use_local | bool) }}"
              sequence:
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ (ai_entity | string) | length > 0 }}"
                      sequence:
                        - action: ai_task.generate_data
                          continue_on_error: true
                          data:
                            entity_id: !input ai_task_entity
                            task_name: "image health check (snapshot)"
                            instructions: "{{ ai_instr }}"
                            structure: "{{ ai_schema | from_json }}"
                            attachments:
                              media_content_id: "{{ media_source_id }}"
                              media_content_type: image/jpeg
                          response_variable: ai
                    - conditions:
                        - condition: template
                          value_template: "{{ (ai_entity | string) | length == 0 }}"
                      sequence:
                        - action: ai_task.generate_data
                          continue_on_error: true
                          data:
                            task_name: "image health check (snapshot)"
                            instructions: "{{ ai_instr }}"
                            structure: "{{ ai_schema | from_json }}"
                            attachments:
                              media_content_id: "{{ media_source_id }}"
                              media_content_type: image/jpeg
                          response_variable: ai

        # If AI failed, call the single script with error (and optionally text‑notify)
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ ai is not defined }}"
              sequence:
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ (script_result | string) | length > 0 }}"
                      sequence:
                        - action: !input script_on_result
                          data:
                            error:
                              message: "no assistant content returned"
                            snapshot:
                              abs_path: "{{ abs_path }}"
                              rel_path: "{{ rel_path }}"
                              provider_mode: "{{ 'local' if (use_local | bool) else 'remote' }}"
                            script_context:
                              camera_entity: "{{ cam }}"
                              ai_task_entity: "{{ ai_entity if (ai_entity | string) | length > 0 else none }}"
                              ts: "{{ now().isoformat() }}"
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ has_notify_action | bool }}"
                      sequence: !input notify_action
                    - conditions:
                        - condition: template
                          value_template: "{{ (not has_notify_action) and has_notify_service }}"
                      sequence:
                        - action: !input notify_service
                          data:
                            title: "AI image health — processing failed"
                            message: >-
                              The AI task did not return a valid assistant message this cycle.
                              Try a different instruct/JSON‑friendly model. (Monitoring continues.)

        # If AI succeeded, optionally send built‑in text alert (threshold‑aware) and call result script
        - variables:
            raw_conf: >-
              {% 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 %}
            norm_conf: >-
              {% if raw_conf is not none %}
                {% set c = raw_conf | float(0) %}{{ (c * 100) if c <= 1 else c }}
              {% else %}{{ none }}{% endif %}
            problem_ok: "{{ ai is defined and (ai.data.has_problem | default(false)) }}"
            threshold_ok: >-
              {% if not threshold_active %}true{% else %}
                {% if norm_conf is none %}false{% else %}{{ (norm_conf | float(0)) >= (conf_threshold | float(0)) }}{% endif %}
              {% endif %}
            should_notify: "{{ problem_ok and threshold_ok }}"
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ should_notify | bool }}"
              sequence:
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ has_notify_action | bool }}"
                      sequence: !input notify_action
                    - conditions:
                        - condition: template
                          value_template: "{{ (not has_notify_action) and has_notify_service }}"
                      sequence:
                        - action: !input notify_service
                          data:
                            title: "⚠️ Image Issue Detected"
                            message: >-
                              Type: {{ ai.data.problem_type | default('unknown') }}
                              Confidence: {{ (norm_conf | float(0)) | round(1) if (norm_conf is not none) else '—' }}%
                              Advice: {{ ai.data.advice | default('Check the print ASAP.') }}
                              (via AI Task)
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ (script_result | string) | length > 0 and ai is defined }}"
              sequence:
                - action: !input script_on_result
                  data:
                    ai_result:
                      has_problem: "{{ ai.data.has_problem | default(false) }}"
                      problem_type: "{{ ai.data.problem_type | default(none) }}"
                      advice: "{{ ai.data.advice | default(none) }}"
                      confidence:
                        raw: "{{ raw_conf if (raw_conf is not none) else none }}"
                        normalized: "{{ norm_conf if (norm_conf is not none) else none }}"
                    snapshot:
                      abs_path: "{{ abs_path }}"
                      rel_path: "{{ rel_path }}"
                      provider_mode: "{{ 'local' if (use_local | bool) else 'remote' }}"
                    script_context:
                      camera_entity: "{{ cam }}"
                      ai_task_entity: "{{ ai_entity if (ai_entity | string) | length > 0 else none }}"
                      ts: "{{ now().isoformat() }}"
                      threshold:
                        active: "{{ threshold_active | bool }}"
                        value: "{{ conf_threshold if threshold_active else none }}"
                      notified: "{{ should_notify | bool }}"

        # Debug (text‑only)
        - choose:
            - conditions:
                - condition: template
                  value_template: "{{ debug_enabled | bool }}"
              sequence:
                - choose:
                    - conditions:
                        - condition: template
                          value_template: "{{ has_notify_action | bool }}"
                      sequence: !input notify_action
                    - conditions:
                        - condition: template
                          value_template: "{{ (not has_notify_action) and has_notify_service }}"
                      sequence:
                        - action: !input notify_service
                          data:
                            title: "AI image health — debug"
                            message: >-
                              {% if ai is not defined %}
                              AI failed this cycle (no assistant content).
                              {% else %}
                              Has Problem={{ ai.data.has_problem | default(false) }},
                              Confidence (raw)={{ raw_conf | default('—') }},
                              Confidence (normalized)={{ (norm_conf | float(0)) | round(2) if (norm_conf is not none) else '—' }},
                              Threshold Active={{ threshold_active }}, 
                              Threshold={{ (conf_threshold | float(0)) | round(1) if threshold_active else '—' }},
                              Notified={{ should_notify | bool }}
                              {% endif %}

        # Wait until next cycle; loop ends when state stops matching
        - delay:
            minutes: "{{ interval | int }}"

Here’s an example script to get you started with notifications:

alias: 3D Print Health Check Results (Minimal)
mode: single
sequence:
  - variables:
      is_error: "{{ error is defined }}"
      has_problem: "{{ ai_result.has_problem | default(false) }}"
      # Optional: normalized confidence if present (0–100)
      conf_norm: >-
        {% if ai_result is defined and ai_result.confidence is defined
           and ai_result.confidence.normalized is defined
           and ai_result.confidence.normalized is not none %}
          {{ (ai_result.confidence.normalized | float) | round(1) }}
        {% else %}{{ none }}{% endif %}
      title: >-
        {% if is_error %}AI Processing Failed
        {% elif has_problem %}⚠️ 3D Print Issue Detected
        {% else %}✅ 3D Print Looks OK{% endif %}
      caption: >-
        {% if is_error -%}
        Error: {{ error.message | default('unknown') }}
        {%- else -%}
        Type: {{ ai_result.problem_type | default('—') }}
        Confidence: {{ (conf_norm ~ '%') if conf_norm is not none else '—' }}
        Advice: {{ ai_result.advice | default('—') }}
        {%- endif %}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ is_error }}"
        sequence:
          - action: persistent_notification.create
            data:
              title: "{{ title }}"
              message: "{{ caption }}"
      - conditions:
          - condition: template
            value_template: "{{ has_problem }}"
        sequence:
          - action: persistent_notification.create
            data:
              title: "{{ title }}"
              message: "{{ caption }}"
2 Likes

Hello Anigeek,
Welcome to the Home Assistant Forum!

This looks cool.
So if I understand it correctly, it is a version of a spaghetti detector, looking for things that are out of pattern and firing a script to do whatever you want with the info?

That’s the idea!

The actual efficacy is totally reliant on the AI, though, so I’m a bit hesitant to say it’ll actually detect spaghetti. It ultimately comes down to what your chosen AI defines as problematic.

Thanks for the welcome!

1 Like

I was ready to upload my Blueprint for the same use case and just discovered yours. :+1:

Are you sure, that you need to use a snapshot file for external AI?
In my Home Assistant instance I use the integration “Google Generative AI” and this works fine with the camera entity.

Example:

  - alias: "AI Analysis of Print Image"
    service: ai_task.generate_data
    data:
      entity_id: !input ai_task_entity
      attachments:
        media_content_id: >-
          media-source://camera/{{ camera_entity_id }}
        media_content_type: image/jpeg
1 Like

It looks like you’re right - when I was first setting this up, I tried using both OpenAI and Gemini integrations and I got an error with both. It didn’t work until I tried Ollama. Unfortunately, I didn’t document the error (it said something about local media), and it looks like it’s working exactly as expected now.

Thanks for calling this out! I’ll update the Blueprint to reflect this (though I still plan on allowing either, 'cuz why not, and it can be useful for notifications).

I’d be interested in seeing what you came up with if you’re still willing to share.

Sure, I just uploaded my Blueprint. I don’t think it’s as comprehensive as your Blueprint.

There your go:

1 Like

how do u get camera.p1s_camera? i just get image.p1s_camera…

Do you have this BambuLab integration installed? GitHub - greghesp/ha-bambulab: A Home Assistant Integration for Bambu Lab Printers
In which version is it installed?

Some month ago they released an update where the camera entity was introduced. Can you check, if you have the newest version installed?

yea i have bambu lab install Version 2.1.27, but my set up are with lan mode? don’t know if it is bc of that?

I haven’t tested with LAN mode myself, unfortunately - it might be worth checking out the GitHub to see if that helps. I also know of at least one person who had problems that was able to resolve them by reinstalling the integration - that might be worth a try if it’s not too disruptive.

Your use case brings up a good point, though - you shouldn’t need a camera, just an image. I’m working on an update (testing takes forever since I try to run a few print cycles to verify everything), and I’ll look into allowing an image entity instead of a camera to fit your scenario.

Just to check - is “Use image sensor camera” off in your setup?

ooh that did the trick!

:smile: Glad it worked!

I’ll still look into adding the image route for the blueprint though, in case there are those with different hardware who need it.

Hope the blueprint helps!

cool! yea thanks i hope it will work :+1:

Hey there! this is my first post on the forum! Lovely blueprint you got there so thank you for sharing.

Unfortunately I am struggling to make it work. I am having issues with the very simplest setup with just the required entities and a simple notify.all_devices notify group.

I am getting the following error when trying to save the automation:

message malformed: Service does not match format <domain>.<name> for dictionary value @ data['actions'][3]['repeat']['sequence'][4]['choose'][0]['sequence'][0]['choose'][0]['sequence'][0]['action']

As mentioned I just added the camera, status and ai entites, set checker every 5 minutes and 85% threshold and added the notifier.

A quick search with both gemini and gpt mentioned that the action calls should be moved to service but did not manage to make it work.

Thank you in advance!

How to read Home Assistant Configuration Errors.
I highly suggest don’t use LLM’s for this. They repeat what they read and are confidently ignorant, they don’t know anything.

1 Like

Hi all. I’m trying to create a automation to see if there is any object before to start to print. The question is, I need to know when the bed is on top.

I’m so sorry that I didn’t see this sooner! I haven’t been able to work on the upgrades I had planned until now, but I’m testing out a complete overhaul now that will make it more configurable and debuggable, and less spaghetti. I hope to have this done in the next few days - if you’re still having problems, please let me know. We can see if 2.0 solves your issue, and if not, what’s going on.

1 Like

Hello! Once I have version 2.0 released (still testing it now), you should be able to use it for pre-print object detection by modifying the AI prompt and response schema. However, it might be simpler for you to just use a custom automation/script and the AI Task action to get that data.

Something like:

alias: Check Printer Obstruction
description: ""
triggers:
  - trigger: state
    entity_id:
      - sensor.bambu_p1s_current_stage
    from:
      - idle
conditions: []
actions:
  - action: ai_task.generate_data
    metadata: {}
    data:
      task_name: Detect Object
      instructions: >-
        You are looking at a video feed of a 3D printer. Determine if an object
        is on the print bed or if the bed is otherwise obstructed.
      structure:
        object_present:
          selector:
            boolean: null
        object_description:
          selector:
            text: null
      attachments:
        media_content_id: media-source://camera/camera.bambu_p1s_camera
        media_content_type: image/jpeg
        metadata:
          title: Bambu P1S Camera
          thumbnail: /api/camera_proxy/camera.bambu_p1s_camera
          media_class: video
          children_media_class: null
          navigateIds:
            - {}
            - media_content_type: app
              media_content_id: media-source://camera
    response_variable: ai_response
  - if:
      - condition: template
        value_template: "{{ ai_response.data.object_present }}"
    then:
      - action: persistent_notification.create
        metadata: {}
        data:
          title: Print Bed Obstruction
          message: "{{ ai_response.data.object_description }}"
          notification_id: print_obstruction
    else:
      - action: persistent_notification.create
        metadata: {}
        data:
          title: Print Bed Clear
          message: No obstruction detected
          notification_id: print_obstruction
mode: single

Thanks a lot. I’m going to try it.
One thing more, can I check the images sent to my API? I would like to improve the knowledge in the replay if I can say it if a message is correct or not