tl;dr: get gif thumbnails notifications on Telegram of your 3D printing job as soon as it’s finished:
Hi there!
About an year ago I got into 3D printing and it was love at first sight 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:
Here’s a full example of a printing job gif:
(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
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!