3D printing timelapse gif notification on Telegram!

tl;dr: get gif thumbnails notifications on Telegram of your 3D printing job as soon as it’s finished:
gif

Hi there!

About an year ago I got into 3D printing and it was love at first sight :slight_smile: a year later I’m publishing a couple nice things I’ve done with my 3D printer. I think you can achieve the same using OctoPrint with a camera, but for the purposes of the post I’ve used a Dremel DigiLab 3D printer model 3D45 (https://amz.run/5jvP). It’s a bit expensive but for 3D printing enthusiasts that want something that just works without having to manually align the platform every time and can let you monitor the printing job out-of-the-box, it may be worth it.

INTEGRATION
After digging into the printer’s internal API, I’ve built the following integration:

Currently it’s available as a default package in HACS. After you add it, add a new integration using the printer’s host IP:

you’ll have access to the following entities:

ENTITIES
The Camera entity is a MJPEG IP Camera that hooks into Dremel 3D45’s built-in camera so you can view the printer’s chamber in real-time. You get Chamber Temperature, Extruder Temperature and Platform Temperature, which are all self descriptive. Door Contact, which is a binary sensor telling you if the ceiling lid or front door are open (so you could correlate chamber temperature with door status, for instance). You get a Progress sensor, which informs you the printing % of the current job. A binary Status, informing you whether the printer is in a running / not running state, and a more detailed Job sensor which tells you in which specific state the printer is currently in (i.e Idle, Preparing, Building, Pausing, Paused, Resuming, Completed, Aborted).

PRINTER DATA AND JOB INFO
There are a couple more metadata about the printer in the Status entity’s attributes:

These fields are also self descriptive. Finally, you can also view data about the current printing job in the Job entity’s attributes:

Some of these fields are exported as sensors, some are not, such as the elapsed/remaining/estimated total time, filament, job name, network build (whether you started a print job through USB or through Wi-Fi) and target temperatures.

SERVICES

Now, you also have a couple of buttons. These buttons do the same thing as the respective services as they are described in this section, so I’ll stick to describing the exported services and you can assume what the button does (for instance, the button Stop Job does the same as the service stop_job. I put the service parameters inside parenthesis, using bold for required fields:

  • stop_job (device)
    Cancels the current printing job on the device.
  • pause_job (device)
    Pauses the current printing job on the device.
  • resume_job (device)
    Resumes the paused printing job on the device.
  • print_job (device, filepath, URL)
    Starts a new printing job on the device using either the given local filepath or a URL, either one pointing to a .gcode or .g3drem file.
  • take_snapshot (device, output directory, filename, show status, snapshot scale)
    Takes a snapshot and saves the JPEG file to the output directory with the given filename, or with a date stamp in case filename is not given. If show status is true, adds some metadata to the snapshot, such as a progress bar, the filament type, the remaining and elapsed time, etc. Snapshot scale will scale the image accordingly, so 0.5 will reduce the image size in half pixels in each dimension, and 2.0 will double its dimension sizes.
  • Add snapshot to gif (device, gif name, show status, snapshot scale)
    Now, I thought this could be a separate integration called Gif Maker or something similar, which takes care of building a gif from a batch of input images. But I wanted to have this feature as soon as possible in this integration so I added it here. It basically works as take_snapshot, but instead of saving it to a specific output directory with a given name, it saves it to the folder .dremel_3d_printer/{job_name}/{id}.jpeg, where job_name is the file name of the current printing job, and id is an integer starting from 0. So, after 50 calls to add_snapshot_to_gif, you will have 50 images named 0 through 49 in that given hidden folder. If gif name is provided, it overrides the job_name with the given gif name.
  • Make gif (device, output directory, gif name, fps, duration)
    Renders the gif using the images inside .dremel_3d_printer/{job_name} or, if gif name is provided, .dremel_3d_printer/{gif name}. FPS has a default value of 10, but you can override it, or you can pass a duration which will override the FPS. FPS = frames per second of the gif; duration = seconds per frame, or duration of each frame in the final gif. Either one is enough to render the gif. After rendering, it will automatically remove the folder with the JPEG images from the file tree, and output the gif file with the given {gif name} (or job_name) to the output directory.

IN PRACTICE
Now, how do I make use of this integration in the front-end? How does the gif services play a part in this whole thing?

Lets first look at how my 3D printer view is currently setup:

When there’s no active printing, this is what you see. As you can see, there’s a card for the Last Job Overview, which gives you the gif of the last printing job.

Now, when you start to print, this is what you see:

Now there are two new cards, one showing the progress % of the platform temperature towards its target (54ºC → 60ºC), and the second showing the extruder temperature % towards its target (34ºC → 230ºC).

Finally, when the printer is actively printing something, this is what you see:

You now see a progress bar and a countdown bar with 5h 28min till the job is complete.

As soon as the job is complete, I get an instant notification on Telegram with the latest 3D printing gif:

gif

Here’s a full example of a printing job gif:

output-onlinegiftools (2)

(full quality: https://home.storhub.io/local/3d_printer_gifs/STD%20Mechanic.gif)

I’m still improving things but this is what I’ve got so far. Hope you enjoyed it :slight_smile:

Now to the scripts:

AUTOMATIONS
Blueprint 1: Add Snapshot to Gif

blueprint:
  name: "Dremel 3D Printer: Add Snapshot to Gif"
  description: Add a snapshot to the printing job gif every time the progress moves to the next integer (i.e 1.7% -> 2.1%, move from integer 1 to integer 2).
  domain: automation
  input:
    3d_printer:
      name: Dremel 3D Printer
      selector:
        device:
          integration: dremel_3d_printer
          manufacturer: Dremel
          model: 3D45
mode: single
trigger:
  - platform: state
    entity_id:
      - sensor.dremel_3d45_progress
condition:
  - condition: template
    value_template: "{{ trigger.to_state.state|int > trigger.from_state.state|int }}"
action:
  - service: dremel_3d_printer.add_snapshot_to_gif
    data:
      scale: 0.5
      show_status: true
      device_id: !input 3d_printer

Blueprint 2: Make Gif and Notify

blueprint:
  name: "Dremel 3D Printer: Make Gif and Notify"
  description: Renders a gif from the added snapshots and notify Telegram with the rendered gif.
  domain: automation
  input:
    3d_printer:
      name: Dremel 3D Printer
      selector:
        device:
          integration: dremel_3d_printer
          manufacturer: Dremel
          model: 3D45
mode: single
trigger:
  - platform: state
    entity_id:
      - sensor.dremel_3d45_job
    to: Completed
    from: Building
condition: []
action:
  - service: dremel_3d_printer.make_gif
    data:
      output_dir: www/3d_printer_gifs
      fps: "20"
      device_id: !input 3d_printer
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - service: telegram_bot.send_document
    data_template:
      file: "/config/www/3d_printer_gifs/{{ state_attr('sensor.dremel_3d45_job', 'job_name') }}.gif"
      caption: 3D printing job finished!

Finally, if you’re interested in my 3D printer dashboard view:

views:
  - theme: Backend-selected
    title: 3D Printer
    path: 3d-printer
    icon: mdi:printer-3d-nozzle
    badges:
      - entity: sensor.dremel_3d45_progress
      - entity: binary_sensor.dremel_3d45_door_contact
      - entity: sensor.dremel_3d45_chamber_temperature
      - entity: sensor.dremel_3d45_platform_temperature
      - entity: sensor.dremel_3d45_extruder_temperature
    cards:
      - type: conditional
        conditions:
          - entity: sensor.dremel_3d45_job
            state: Building
        card:
          type: vertical-stack
          cards:
            - type: custom:bar-card
              entities:
                - name: '> Progress'
                  min: 0
                  max: 100
                  unit_of_measurement: '%'
                  entity: sensor.dremel_3d45_progress
                  icon: fas:percent
                  color: '#40E0D0'
              animation:
                speed: 1
            - type: custom:timer-bar-card
              entities:
                - sensor.dremel_3d45_job
              active_state:
                - Building
              pause_state:
                - Pausing
                - Paused
              waiting_state:
                - Idle
              guess_mode: true
              start_time:
                entity: var.dremel_3d45_job_start_time
              end_time:
                entity: var.dremel_3d45_job_end_time
            - type: custom:mushroom-chips-card
              chips:
                - type: template
                  icon: far:clock
                  content: >-
                    {% set duration = state_attr('sensor.dremel_3d45_job',
                    'estimated_total_time') %} {% set seconds = duration % 60 %}
                    {% set minutes = (duration / 60)|int % 60 %} {% set hours =
                    (duration / 3600)|int %} Total Estimated {% if hours > 0 %}
                      {{ '{:02d}h {:02d}m {:02d}s'.format(hours, minutes, seconds) }}
                    {% elif minutes > 0 %}
                      {{ '{:02d}m {:02d}s'.format(minutes, seconds) }}
                    {% else %}
                      {{ seconds }}s
                    {% endif %}
                - type: template
                  icon: far:clock
                  content: >-
                    {% set duration = state_attr('sensor.dremel_3d45_job',
                    'elapsed_time') %} {% set seconds = duration % 60 %} {% set
                    minutes = (duration / 60)|int % 60 %} {% set hours =
                    (duration / 3600)|int %} Elapsed {% if hours > 0 %}
                      {{ '{:02d}h {:02d}m {:02d}s'.format(hours, minutes, seconds) }}
                    {% elif minutes > 0 %}
                      {{ '{:02d}m {:02d}s'.format(minutes, seconds) }}
                    {% else %}
                      {{ seconds }}s
                    {% endif %}
                - type: template
                  icon: far:clock
                  content: >-
                    {% set duration = state_attr('sensor.dremel_3d45_job',
                    'remaining_time') %} {% set seconds = duration % 60 %} {%
                    set minutes = (duration / 60)|int % 60 %} {% set hours =
                    (duration / 3600)|int %} Remaining {% if hours > 0 %}
                      {{ '{:02d}h {:02d}m {:02d}s'.format(hours, minutes, seconds) }}
                    {% elif minutes > 0 %}
                      {{ '{:02d}m {:02d}s'.format(minutes, seconds) }}
                    {% else %}
                      {{ seconds }}s
                    {% endif %}
      - show_state: true
        show_name: true
        camera_view: auto
        type: picture-entity
        entity: camera.dremel_3d45_camera
        camera_image: camera.dremel_3d45
      - type: conditional
        conditions:
          - entity: sensor.dremel_3d45_job
            state: Preparing
        card:
          type: vertical-stack
          cards:
            - type: custom:mushroom-template-card
              primary: >-
                {% set platform_temp = state_attr('sensor.dremel_3d45_job',
                'platform_temperature')|float %} {% set target_temp =
                state_attr('sensor.dremel_3d45_job',
                'platform_target_temperature')|float %} Platform Temperature ({{
                '%.2f' % (100.0 * (min(platform_temp, target_temp) /
                max(platform_temp, target_temp))) }}%)
              secondary: >-
                {% set platform_temp = state_attr('sensor.dremel_3d45_job',
                'platform_temperature') %} {% set target_temp =
                state_attr('sensor.dremel_3d45_job',
                'platform_target_temperature') %} {{ platform_temp }}ºC –>
                {{target_temp}}ºC
              icon: mdi:printer-3d
            - type: custom:mushroom-template-card
              primary: >-
                {% set extruder_temp = state_attr('sensor.dremel_3d45_job',
                'extruder_temperature')|float %} {% set target_temp =
                state_attr('sensor.dremel_3d45_job',
                'extruder_target_temperature')|float %} Extruder Temperature ({{
                '%.2f' % (100.0 * (min(extruder_temp, target_temp) /
                max(extruder_temp, target_temp))) }}%)
              secondary: >-
                {% set extruder_temp = state_attr('sensor.dremel_3d45_job',
                'extruder_temperature') %} {% set target_temp =
                state_attr('sensor.dremel_3d45_job',
                'extruder_target_temperature') %} {{ extruder_temp }}ºC –>
                {{target_temp}}ºC
              icon: mdi:printer-3d
      - type: custom:mushroom-template-card
        primary: '{{ state_attr(''sensor.dremel_3d45_job'', ''job_name'') }}'
        secondary: '{{ states(''sensor.dremel_3d45_job'') }}'
        icon: fapro:smart-devices/3d-printer#fullcolor
      - type: conditional
        conditions:
          - entity: sensor.dremel_3d45_job
            state_not: Building
        card:
          type: vertical-stack
          cards:
            - type: markdown
              content: >
                ## Last Job Overview

                Gif: /local/3d_printer_gifs/{{
                states('var.dremel_3d45_last_job_name') }}.gif
            - type: custom:config-template-card
              entities:
                - var.dremel_3d45_last_job_name
              variables:
                JOB_NAME: states['var.dremel_3d45_last_job_name'].state
              card:
                type: picture
                image: ${'/local/3d_printer_gifs/' + JOB_NAME + '.gif'}

That’s it folks, any feedbacks are welcome!

4 Likes

This looks amazing !
Must try when I get my printer set up in its new home !!!

Amazing!!!
I will try this today

1 Like

Does anybody know how to do it on GEEETECH A20M 8 Best Dual Extruder 3D Printers: In-Detail Reviews (Fall 2022)? I bought it recently and still not quite figured out how to do such complex things

Integration seems to be not working anymore. It just does not find my 3D40 :frowning: