Food Labels via a Dymo Label Printer

We’ve always labelled food which goes in the freezer, it makes a freezer rummage much easier, and avoid mystery meals.

The Dymo printer does a decent job, but it’s really limited to desktop computers (unless you buy yet another dymo), which makes it a pain to print the food labels. My handwriting beyond abysmal…

Obligatory Video

First, there isn’t a home assistant integration for a dymo label printer, so we’re in to dirty hacks pretty quickly.

First, setup lprint on a linux box, the instructions seemed to work fine for my LabelWriter 450 Duo.

Next problem, lprint it doesn’t take text files directly, You’ll see it needs PNG files, which ImageMagick can do relatively easily. It doesn’t automatically resize based on content length, so I added a little bit of branch logic based on the size of the first line. The last line finishes with the number of labels required

wishy@niska:~$ cat printlabel.sh
#!/bin/bash

declare -a lines

readarray -d "\n" lines <<< "$1"

char_count=${#lines[0]}

label=$(head -c -3 <<<"$1")
prints=$(tail -c 2 <<<"$1")

if [[ $char_count -lt 13 ]]; then
  convert -size 673x378 xc:white -font "AvantGarde-Book" -pointsize 96 -fill black -annotate +30+100 "$label" image.png
else
  convert -size 673x378 xc:white -font "AvantGarde-Book" -pointsize 64 -fill black -annotate +30+130 "$label" image.png
fi
lprint image.png -o media=oe_md-multipurpose-label_2.25x1.25in -n "$prints"

We can test using

./printlabel.sh 'Toad in the Hole\n2 Portion\n10/03/2024,1'

Now, how do we hook that up to home assistant. First, write a short calling script. This SSHs to the linux box and runs the print command, you’ll need to set up SSH keys for authentication from the homeassistant user. I added a little escaping for ’ as is breaks the script

root@nandi:/home/homeassistant# cat printlabel.sh
#!/bin/bash
MESSAGE=$(cat)
CLEAN_MESSAGE=$(echo "$MESSAGE" | tr -d "'")
/usr/bin/ssh [email protected] "./printlabel.sh '$CLEAN_MESSAGE'"

And we can hook that up via command line notify

command_line:
  - notify:
      command: /home/homeassistant/printlabel.sh
      name: "Print Dymo Label"

Finally, the voice bit! We could just work directly from a single prompt, but I decided that I probably didn’t want labels printed until I’ve had a chance to see what speech to text has come back with. Hence one automation to create labels and save these to helpers

alias: Dymo Create Label by Voice
description: ""
trigger:
  - platform: conversation
    command:
      - >-
        [Create|Print] [a] [dymo|food] label {quantity} [portions|portion] [of]
        {food} 
condition: []
action:
  - service: input_text.set_value
    metadata: {}
    data:
      value: "{{ trigger.slots.food |title}}"
    target:
      entity_id: input_text.dymo_food_label
  - service: input_text.set_value
    metadata: {}
    data:
      value: "{{ trigger.slots.quantity }}"
    target:
      entity_id: input_text.dymo_food_portion
  - set_conversation_response: OK, {{ trigger.slots.food | title}}. Say 'print 2 labels'
mode: single

and another to actually print. I had some problems here, I need to pass an integer number of prints to the script, but speech-to-text tends to use words rather than digits. It would be handy if custom sentence could somehow be cast as integers with some reasonably logic around it, but for the moment, I’ve just bodged some find and replace logic for two->2. I also found some words are commonly mistaken by speech-to-text (Too instead of two, Free instead of Three) and hard coded some corrections in

alias: Dymo Label - Print Food Label (Voice)
description: ""
trigger:
  - platform: state
    entity_id:
      - input_button.dymo_print_label
  - platform: conversation
    command:
      - Print {number} Labels
      - Print Label
      - Print [1|one|a] [Label|Labels]
condition: []
action:
  - if:
      - condition: template
        value_template: "{{ trigger.slots.number|length != 0 }}"
      - condition: template
        value_template: >-
          {{trigger.slots.number|replace('one','1')|replace('to','2')|replace('too','2')|replace('two','2')|replace('free','3')|replace('three','3')|replace('four','4')|replace('five','5')|replace('six','6')|int<7}}
    then:
      - service: notify.print_dymo_label
        data:
          message: >-
            {{states('input_text.dymo_food_label')}}\n{{states('input_text.dymo_food_portion')}}
            Portion\n{{now().day}}/{{now().month}}/{{now().year}},{{trigger.slots.number|replace('one','1')|replace('two','2')|replace('to','2')|replace('too','2')|replace('free','3')|replace('three','3')|replace('four','4')|replace('five','5')|replace('six','6')}}
    else:
      - service: notify.print_dymo_label
        data:
          message: >-
            {{states('input_text.dymo_food_label')}}\n{{states('input_text.dymo_food_portion')}}
            Portion\n{{now().day}}/{{now().month}}/{{now().year}},1
mode: single

Final result, it works, even if there are some dirty hacks.
As far as I can see I’m the first to hook up a Dymo to home assistant, so I hope it’s useful for others in the future.

Truly awesome … I LOVE IT!

The results of the contest are out!
They may be of interest to you :wink:
Have a look!

1 Like

Love this! Finally a way to get rid of that pesky Dymo software.

I made a few changes to your script to make it work better for my use-case:

#!/bin/bash

declare -a lines

readarray -d "\n" lines <<< "$1"

char_count=${#lines[0]}

label=$(head -c -3 <<<"$1")
prints=$(tail -c 2 <<<"$1")

convert -size 673x378 -background white -fill black -font "AvantGarde-Book" -gravity Center caption:"$label" image.png
lprint image.png -n "$prints"

Using caption:

  • the content now is now auto wrapped
  • when specified without a pointsize, the content is scaled to the maximum available size

I encouraged the lprint author to make a docker compatible version, then we could add this as a HA add-on. Great work!

So, after spending some time with your work I think I simplified some stuff and made it work a little better for me.

Shell script

On my HA install, I created a shell script which remotely executes all commands over SSH.

/config/labelprinter/printlabel.sh:

#!/bin/bash
ssh -o StrictHostKeyChecking=accept-new -i /config/.ssh/id_ed25519 [email protected] "convert -size 510x280 -background white -fill black -font 'AvantGarde-Book' -gravity Center caption:'$1' label.png;/snap/bin/lprint label.png -n '$2'"

Requires SSH keys generated and saved in in /config/.ssh

Shell command

This shell script is executed in turn by the shell command integration.

Config.yaml:

shell_command:
  print_label: "/config/labelprinter/printlabel.sh '{{content}}' '{{copies}}'"

Creating helpers:

  • input_button.print_text_label
  • input_text.label_text
  • input_number.label_copies

Creating the automation:

alias: Print text label
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_button.print_text_label
conditions: []
actions:
  - action: shell_command.print_label
    data:
      content: "{{ states('input_text.label_text', with_unit=True) }}"
      copies: "{{ states('input_number.label_copies') | round(0) }}"
mode: queued
max: 10

Thanks again for your work! Hope this benefits others.

2 Likes

Hey @wishy - this is awesome! I’m planning on setting it up myself. I noticed that you’re using a Dymo 450 Duo. Do you have it currently setup so that you can print both types of labels? Or do you only use the larger slot?

Hey, Sorry, I missed these posts.

@wildekek those are some nice modifications, I’ll look to adopt those. My attempt was very much “hack something together”

@rohankapoorcom I am indeed using a 450 Duo, I’ve only set it up to use the top slot (Larger Thermo-Ink labels). I don’t see a good reason that lprint couldn’t use the standard 12mm “dymo tape”, but the dymo tape is a lot more expensive than the labels, and are made of plastic. It feels a little inefficent to use them for this particular usecase, but other tasks like asking for network cable labels and soforth could work

No worries, I meant to post back earlier with my modifications (based off of @wildekek’s work) and then forgot to. I’ve set things up so that I can print from either the top slot or the bottom slot depending on what’s selected. I haven’t hooked up the voice part yet, but am controlling it via a little dashboard I built for it. I also added a date picker and a on/off toggle to print the date (automatically if selected) to make it a little easier to use for my use case.

My shell scripts are linked here. I have one for the regular label and one for the tape label. They directly ssh to the lprint machine (A Raspberry Pi 4 8GB, but smaller units should work fine) and then generate the image and print it all within the script.

My automations and shell_commands live in this package. There’s one automation for each of the print buttons (one for the main label and one for the tape label). Another automation sets the date of the input_datetime to today every day.

I created the following helpers:

  • input_number.label_copies
  • input_text.label_text
  • input_datetime.label_date
  • input_boolean.print_date
  • input_button.print_main_label
  • input_button.print_tape_label

Here’s what it looks like on my dashboard:

The dashboard uses several custom cards (from HACS):

  • stack-in-card
  • card_mod
  • layout-card
  • button-card

And the dashboard yaml if it’s helpful for anyone.

type: custom:stack-in-card
cards:
  - type: custom:stack-in-card
    cards:
      - type: entities
        show_header_toggle: false
        entities:
          - entity: input_text.label_text
            name: Text
          - entity: input_number.label_copies
            name: Copies
        state_color: false
        card_mod:
          style: |
            .card-content {
              padding-bottom: 0px;
            }
      - square: false
        type: grid
        columns: 2
        cards:
          - type: entities
            entities:
              - entity: input_datetime.label_date
                name: Date
                card_mod:
                  style:
                    hui-generic-entity-row:
                      $: |
                        .info {
                          margin-left:0 !important;
                          display: none;
                        }
                    ha-date-input $: |
                      ha-svg-icon {
                          display: none;
                      }
            card_mod:
              style: |
                .card-content {
                  padding-top: 0px;
                }           
          - type: entities
            entities:
              - entity: input_boolean.print_date
                name: Include Date
                icon: mdi:calendar-badge
                card_mod:
                  style:
                    hui-generic-entity-row:
                      $: |
                        state-badge {
                          display: none;
                        }
                        .info {
                          margin-left:0 !important;
                        }
                    .: |
                      hui-generic-entity-row {
                        min-height: 56px;
                      }
            card_mod:
              style: |
                .card-content {
                  padding-top: 0px;
                }           
  - type: custom:layout-card
    layout_type: vertical
    cards:
      - type: custom:button-card
        name: Print Label
        icon: mdi:printer
        layout: icon_name
        tap_action:
          action: call-service
          service: input_button.press
          target:
            entity_id: input_button.print_main_label
        styles:
          card:
            - height: 70px
            - border-radius: var(--ha-card-border-radius, 4px)
            - '--ha-card-background': >-
                linear-gradient(to right, rgba(0, 0, 0, 0) -10%,
                var(--slider-color) 100%)
      - type: custom:button-card
        name: Print Tape Label
        icon: mdi:paper-roll
        layout: icon_name
        tap_action:
          action: call-service
          service: input_button.press
          target:
            entity_id: input_button.print_tape_label
        styles:
          card:
            - height: 70px
            - border-radius: var(--ha-card-border-radius, 4px)
            - '--ha-card-background': >-
                linear-gradient(to right, rgba(0, 0, 0, 0) -10%,
                var(--slider-color) 100%)
title: Print Labels
card_mod:
  style: |
    ha-card {
      padding: 0px 10px 15px 10px;
    }

I love the home assistant community, it might be only 3 people who want this, but by god have we got a pretty and over-engineered a solution :slight_smile:

1 Like