Home Assistant Printer Power Management
Tags: homeassistant, linux, mqtt, printer
Published: 2021-06-06 19:54:13

---
title: Home Assistant Printer Power Management
published: true
tags: homeassistant, linux, mqtt, printer
canonical_url: https://unixorn.github.io/post/home-assistant-printer-power-management/
---

I've got an old HP laser printer in my basement. We barely print 10 pages a month between the two of us, so we only turn it on when we're going to print. That's a hassle though, because inevitably we forget to shut it off sometimes and it stays on overnight or even for days, and while it has a powersave mode, the 4050N is so old that even that burns a good amount of power.

Enter Home Assistant.

## Prerequisites

### You have HA configured to connect to a MQTT server

The watcher script and associated tooling all presume that we can send messages to a MQTT topic that HA is watching.

### Your printer is connected to a cupsd server running in a container

Your computers should be configured to print to the cupsd server instead of directly to the printer.

I run `cupsd` in a container on one of my Odroids. I could run it on the same Odroid HC2 that I run Home Assistant (HA) on, but there's no compelling reason to do so and I'm reserving that node for strictly HA containers like Home Assistant itself and my MQTT server. I picked an Odroid because it has a SATA drive attached and my `/var/lib/docker` is on the hard drive and not an microSD card - there's no reason you can't run it on a Raspberry Pi other than to prevent excessive wear on the microSD card.

You could modify the watcher script if you're running `cupsd` directly instead of in a container, but I run my `cupsd` in a container, so that's what the script is designed for.

There are plenty of articles about setting up `cupsd`, but I wrote about setting up `cupsd` [here](https://unixorn.github.io/post/cupsd-setup/).
### Your printer is plugged into an outlet controlled by HA

We want to be able to toggle the power from Home Assistant.

## Printer Power Control

### mosquitto helper script

I don't like to install anything more on my docker hosts than I absolutely have to, so instead of installing mosquitto directly on the printserver machine, I run `mosquitto_pub` inside a container with the following `c-mosquitto_pub` helper script. You can download it from github [here](https://github.com/unixorn/blog-scripts/blob/master/cupsd-hass/c-mosquitto_pub). Put this in `/usr/local/bin`.

```bash
#!/usr/bin/env bash
#
# Use docker to run mosquitto_pub
#
# Copyright 2019, Joe Block <jpb@unixorn.net>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

exec docker run -t --rm eclipse-mosquitto mosquitto_pub $@
```

### cupsd Watcher

Once I had cupsd configured to share the printer (as Franklin), I wrote a quick script that checks the print queue to see if it is empty or not. If there are jobs in the queue, it writes **ON** to an MQTT topic, `hass/printers/franklin`. If the queue is empty, it writes **OFF**. The examples here all assume your printer is named Franklin, replace Franklin with your printer's name.

Actually, I lied. When there are jobs, it writes **OFF** and _then_ **ON**.

Why? Because I don't want HA to switch the printer off immediately once the queue drains - the printer has enough RAM that there may still be several pages left to print when it has accepted all of the job from the server.

Instead, I've configured HA to restart a timer every time it sees the MQTT topic `hass/printers/franklin` switch from **OFF** to **ON**, and only turn the printer off after the queue has been empty for five continuous minutes.

Here's the `ha-check-for-print-jobs` script source - you can download it from github [here](https://github.com/unixorn/blog-scripts/blob/master/cupsd-hass/ha-check-for-print-jobs).

Put the script in `/usr/local/bin` on the same server you're running the cupsd container on - it is designed to run a tool inside that container.

```bash
#!/usr/bin/env bash
#
# ha-check-for-print-jobs
#
# Check if there are print jobs on $PRINT_Q. If there are, write
# MQTT messages to a watched topic so HA knows to turn on the power
# to the printer.
#
# Copyright 2021, Joe Block <jpb@unixorn.net>
#
# License: Apache 2

set -o pipefail

# Make all these overridable easily in your cron setup
PRINT_Q=${PRINT_Q:-'Franklin'}
CONTAINER=${CONTAINER:-'cupsd-server'}
MQTT_HOST=${MQTT_HOST:-'mqtt.example.com'}
MQTT_TOPIC=${MQTT_TOPIC:-'hass/printers/franklin'}

# We are run out of cron every minute, but I don't want it to take an
# entire minute to turn on the power because I'm impatient and the printer
# takes a bit to start up. When we print and walk downstairs, I want it
# to have already started printing by the time I get there. If I was
# patient, I wouldn't have bothered to write this tool :-)
#
# So, when we get run by cron, we check the queue CHECK_COUNT times, with
# CHECK_DELAY seconds between each run.
CHECK_COUNT=${CHECK_COUNT:-'11'}
CHECK_DELAY=${CHECK_DELAY:-'5'}

export PATH="$PATH:/usr/local/bin:/usr/local/sbin"

if [[ -f /tmp/printerdebug ]]; then
  DEBUG='true'
fi

# Only spam syslog when DEBUG is set
debugout() {
  if [[ -n "$DEBUG" ]]; then
    echo "$@"
  fi
}

validate-settings(){
  debugout "CONTAINER: $CONTAINER"
  debugout "PRINT_Q: $PRINT_Q"
  debugout "MQTT_HOST: $MQTT_HOST"
  debugout "MQTT_TOPIC: $MQTT_TOPIC"

  valid='true'
  if [[ -z "$CONTAINER" ]]; then
    echo "CONTAINER is unset"
    valid='false'
  fi
  if [[ -z "$PRINT_Q" ]]; then
    echo "PRINT_Q is unset"
    valid='false'
  fi
  if [[ -z "$MQTT_HOST" ]]; then
    echo "MQTT_HOST is unset"
    valid='false'
  fi
  if [[ -z "$MQTT_TOPIC" ]]; then
    echo "MQTT_TOPIC is unset"
    valid='false'
  fi
  if [[ "$valid" == "false" ]]; then
    echo "Configure your settings."
    exit 1
  fi
}

print-job-checker() {
  printjobs=$(docker exec -t "$CONTAINER" lpq -P "$PRINT_Q" | grep -c 'no entries')

  if [[ "$printjobs" == '1' ]]; then
    debugout "No jobs in print queue, notifying HA"
    c-mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" -m OFF
  else
    echo "jobs found in print queue, notifying HA"

    docker exec -t "$CONTAINER" lpq -P "$PRINT_Q"

    # Set the status off, then back to on, so that the HA timer restarts
    # and HA doesn't turn off the printer in the middle of a job
    c-mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" -m OFF
    c-mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" -m ON
    debugout "re-enabling printer $PRINT_Q..."
    docker exec -t "$CONTAINER" lpadmin -p "$PRINT_Q" -o printer-error-policy=retry-current-job
  fi
}

validate-settings

# We run the print-job-checker every 5 seconds to minimize the UI delay on the
# macOs end

for i in $(seq $CHECK_COUNT)
do
  print-job-checker
  debugout "waiting..."
  sleep $CHECK_DELAY
done
```

### Home Assistant Setup

I configured my HA to watch a MQTT topic as a binary sensor. You can download this snippet [here](https://github.com/unixorn/blog-scripts/blob/master/cupsd-hass/printer-binary-sensor.yaml).

```yaml
binary_sensor:
  - platform: mqtt
    name: "Franklin Print Queue"
    payload_on: "ON"
    state_topic: "hass/printers/franklin"
```

Now, when the watcher writes **ON** and **OFF** to the `hass/printers/franklin` queue, that binary sensor will change status and we can trigger an automation for it.

This automation will turn the printer power on every time the binary sensor is turned on, and turn it off five minutes after the last time the binary sensor switched from **ON** to **OFF**.

The outlet my printer is plugged into is controlled by HA and rather unimaginatively named `switch.printerpower`.

Add this stanza to your automations.yaml file. Download it [here](https://github.com/unixorn/blog-scripts/blob/master/cupsd-hass/printer-automations.yaml).

```yaml
# Franklin power is controlled by MQTT
  - alias: 'Turn on Franklin when there are jobs in the queue'
    trigger:
      platform: state
      entity_id: binary_sensor.franklin_print_queue
      to: 'on'
    action:
      service: homeassistant.turn_on
      entity_id: switch.printerpower

  - alias: 'Turn off printer 5 minutes after print queue drains'
    trigger:
      platform: state
      entity_id: binary_sensor.franklin_print_queue
      to: 'off'
      for:
        minutes: 5
    action:
      service: homeassistant.turn_off
      entity_id: switch.printerpower
```

### Test the pieces

#### Print server check

Confirm that you've got the print queue configured correctly by running `docker exec -it cupsd-server lpq -P Franklin`. If there are no jobs, it should print something like

```
Franklin is ready
no entries
```

#### Automation test

Reload your automations, and you should now be able to test that the automations are correct by running `c-mosquitto_pub -h mqtt.yourdomain.com -t hass/printers/franklin -m OFF` or `-m ON` and watch HA turn the power to your printer off and on.

Once that is working, print a job, and if you run `ha-check-for-print-jobs` the printer power should get turned on.


### Run it all automatically

Now that you've confirmed that the power is being cycled properly when the MQTT queue recieves messages and that the print job checker is seeing the printer queue, we can add the checker job to cron.

Add
```shell
* * * * * PRINT_Q=Franklin MQTT_HOST=mqtt.example.com MQTT_TOPIC=hass/printers/franklin CONTAINER=cupsd_server /usr/local/bin/ha-check-for-print-jobs | logger -t printserver
```

to your `/etc/crontab`, and you're good to go. Now every minute, the checker script will get run by `cron`, and it will check every five seconds for print jobs and exit before the next invocation by `cron`.