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.
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. Put this in /usr/local/bin
.
#!/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.
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.
#!/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.
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.
# 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
* * * * * 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
.