Notification when printing *actually* completes

published: 2017-07-07 22:12:38, updated: 2017-07-07 22:22:52
automationbashscript;
a script to notify when the printer actually finishes the printing job

image for Notification when printing *actually* completes

My desk in the lab (or office) is at one end, near the door. The nearest printer is at the end other end of the room, about 10 meters away. Whenever I print something, the printer, one of those big laser ones, emits some typical sounds of printing. I have to wait until those sounds stop to determine my print job has completed. Sometimes, there are several people printing at once, and it becomes impossible to tell when my job in particular has completed printing. The status on my machine is not entirely accurate, because it only tells when a print job has been accepted by the printer, not when it actually finishes printing. Most people give their print order and simply walk to the printer after 5-10 minutes to pick up their printouts. But I wanted a more elegant solution, something befitting my training as a researcher and an engineer. So I set out to write a script that can notify me when the printing finishes on the printer.

The inspiration for this came from one of those stories on the internet. This one went something like this - guy in office wrote a lot of scripts to automate things like messaging his manager when he was late based on whether he was logged into the office computer, setting up the coffee machine to make the brew in the exact time he took to walk over, and several others.

One of the first challenges I faced was to determine what job was printing on the printer. Since the printer is always connected to the network, I looked for a way to query it. Laser printers have a network interface accessible at their IP address. This is an online webpage hosted by the printer (yes, the printer is acting as a server) that details status information, admin configurations, and also shows the current printing status. The printer in our office is a Lexmark T650, whose status webpage does not show which job is printing, nor is there a log of all print jobs.

There is a protocol called Simple Network Management Protocol (SNMP) for managed devices on the network such as routers, modems, servers, and importantly - printers. The details of what mechanisms are available for querying over the protocol are detailed in a Management Information Base (MIB). Often times, printer manufacturers use private MIBs to detail query string that their printers interact with. I got to know about this protocol thanks to /u/cocoabean.

The Lexmark MIB has a section called opsys that provides various operational status information on the printer. In particular, the MIB of interest is opsysCurrentJob with OID .1.3.6.1.4.1.641.1.1.3 which is defined as -

"A textual description of the currently printing job containing the Source NOS, Source server, Source user, Job number, and Job size, separated by CR LF. A NULL string indicates no active job."

The format of the response is of the form -

STRING: 
"TCP/IP 134.226.63.214,42524
Port 9100
357
Unknown"

The IP address part of the response is the address of the machine that sent that job. So it will be my IP address if my job is being printed. The way to detect when my job has finished printing is to detect the context change of the current job no longer having my IP address.

To query the printer, I used the tool snmpwalk which allows querying printers using the SNMP protocol. It can be used as -

snmpwalk -v 2c -c public <PRINTER-IP> <MIB-OID>

The script needs a way to determine what the last job status was - if it was printing my job or not. One alternative is to keep the script running in the background at all times, thereby keeping the variables in memory, but this solution is not elegant as it leaves a process running. Another option is to store the status of the previous job somewhere. I chose /tmp as it is a temporary storage that gets cleaned up automatically, but never in the middle of usage.

Getting the previous status, whether the printer is currently printing, and whether the print job has my IP, the following states are possible:

| Previous state | Currently printing | Printing my job |   Action   |
| :------------: | :----------------: | :-------------: | :--------: |
|       Y        |         Y          |        Y        |     --     |
|       N        |         Y          |        Y        |     --     |
|     **Y**      |       **Y**        |      **N**      | **notify** |
|       N        |         Y          |        N        |     --     |
|     **Y**      |       **N**        |      **Y**      | **notify** |
|       N        |         N          |        Y        |     --     |
|     **Y**      |       **N**        |      **N**      | **notify** |
|       N        |         N          |        N        |     --     |

Summarising all conditions in which the script should notify -

if  [ $previous_state == true ] && \
    [ $currently_printing == false || $printing_myjob == false ];
then
    notify "print complete"
fi

The overall algorithm goes something like this -

  1. Retrieve my IP address`
  2. Retrieve current printer job string
  3. If it is empty, then set currently_printing and printing_myjob to false
  4. Else
    1. set currently_printing to true
    2. If job IP is same as my IP, set printing_myjob to true
    3. Else set printing_myjob to false
  5. Read previous state from file /tmp/printerpreviousstate into previous_state
  6. If notification condition is satisfied, generate notification

To notify, I used zenity, although several alternatives exist. Another option that uses the system notification panel (if it exists) is notify-send. Both work fine and which one to choose is a matter of preference.

I put the script up as a cron job running every 5 seconds. While cron runs jobs every minute, I created multiple entries with delays using sleep -

* * * * * ~/bin/printjob_status.sh                                              
* * * * * sleep 5; ~/bin/printjob_status.sh                                     
* * * * * sleep 10; ~/bin/printjob_status.sh                                    
* * * * * sleep 15; ~/bin/printjob_status.sh                                    
* * * * * sleep 20; ~/bin/printjob_status.sh                                    
* * * * * sleep 25; ~/bin/printjob_status.sh

I ran into some issues with displaying notifications from inside a cron script. To get around that, I had to export a variable called DBUS_SESSION_BUS_ADDRESS and target the notification explicitly to a display using DISPLAY=:0. The reasons for this elude me.

In the end, I managed to get the script running, and to how a notification informing me that my printing job was complete. For now, I'm happy, though I'm sure I will probably tweak it some more in the future.