Author: atax1a
Mode: perl
Date: Fri, 16 Dec 2022 07:44:34
Plain Text |
#!/usr/bin/perl -w
use strict;
use IO::Socket::INET;
use List::Util qw(first);

my $DEBUG = undef;
# Purpose:
# After syncing with the printer (the name or IP of which is passed on the
# command line), wait for it to be in a READY state, acquire the page count,
# and print it on STDOUT.
# Languages used:
# Printer Job Language (to request status updates of our printers)
# PostScript (to read the page count register)

# Tunable constants
my $TIME = 10; # Number of seconds to wait before expecting a USTATUS message
my $READLEN = 512; # Number of bytes to sysread() from the printer to try and
                   # capture the ustatus.
my $MAX_PAGECOUNT_RETRIES = 3; # Number of times to try and read the pagecount
                               # out of the printer.

# Manifest constants
my $CRLF = qq{\cM\cJ};   # Preferred line endings

# Constants for HP PJL. The meanings, syntax, and semantics of this language
# are documented here:
# A brief summary follows each request, but please see the documentation if you
# work on this.

# The PJL Universal Escape Language.
my $PJL_ESCAPE     = qq{\033%-12345X};

my $PJL_USTATUS_ON = $PJL_ESCAPE . '@PJL COMMENT Sync Printer' . $CRLF . # Enter PJL mode
                     # Disable any other status messages
                     '@PJL USTATUSOFF' . $CRLF .
                     # Ask for status about the device
                     '@PJL USTATUS DEVICE = ON' . $CRLF .
                     # ... Every $TIME seconds
                     '@PJL USTATUS TIMED  = ' . $TIME . $CRLF .
                     # Also ask the printer for what it thinks it is.
                     '@PJL INFO ID' . $CRLF;

# The following PostScript job reads the page count register, and reports it in
# an easily-distinguished format on the printer's communication channel (in
# this case, our network socket)
(%%[ pagecount: ) print
statusdict /pagecount get exec % Get the page count register
(                ) cvs print   % Stuff it into the string
( ]%%) =                       % Write it out

# If our PJL INFO ID matches this, we  need a different code.
my $PHASER_WORKAROUND = qr/\@PJL INFO ID\cM?\cJXerox Phaser /ms;

# PJL State constants:
# Ready, Toner Low, has-a-job (lprng leaves it in this state for some reason)
my @GOOD_CODES = (10001, 10006, 10030);

# PJL "we should sleep longer" codes:
# Power save, warming up, power save.
my @SLEEP_LONGER = (10000, 10003, 35078);

# Pull off the command-line argument. There is only one required, and it should
# be the name or IP address of a printer from which to read the page count.
my $printername = shift || die "$0 printername\n";

# Connect to the printer's JetDirect socket, or error.
my $printer = IO::Socket::INET->new(
        PeerAddr => "$printername:9100",
        Proto => "tcp",
        Timeout => 30)
    or die "Couldn't connect to $printername: $!\n";

my $buf; # A temporary buffer used for reading data out of the printer.
my $hist; # used after requesting the page count; holds the previous buffers

# Request timed unsolicted updates as well as printer identification
$printer->syswrite($PJL_USTATUS_ON) || die "$0: $printername went away? ($!)\n";
print STDERR "Connected to printer and sent PJL ustatus request\n" if $DEBUG;

sleep 1; # Wait for the printer to respond.

$printer->sysread($buf, $READLEN*2) || die "$0: $printername went away? ($!)\n";
if($buf =~ $PHASER_WORKAROUND) {
    # We've got a Phaser, which needs 10023 as a "good" code.
    print STDERR "Phaser workaround enabled\n" if $DEBUG;

# Main loop
RETRY: while(1) {
    $buf = ""; # Clear buffers
    $hist = "";
    my $code = 0;

    $printer->sysread($buf, $READLEN) || die "$0: $printername went away? ($!)\n";
    print STDERR "Got this buffer: $buf\n" if $DEBUG;
    if ($buf =~ m/CODE=(\d{5})/ims) {
        $code = $1;
    if(not $code or not first {$_ == $code} @GOOD_CODES) {
        # These are things we do not want, so we wait for the system to report
        # ready status with a good code.
        warn "Didn't get an expected good code, instead received $code\n";
        sleep $TIME;
        next RETRY;
    if(first {$_ == $code} @SLEEP_LONGER) {
        # These are okay, but we need to chill for a bit. The printer needs some
        # time to wake up.
        sleep $TIME * 2;
        next RETRY;

    # Right now, we're guaranteed a "good" code. Send the pagecount job and its
    # trailing EOF, and then the command to return to PJL mode.
    $printer->syswrite($PAGECOUNT_PS . "\cD" .$PJL_ESCAPE) ||
        die "$0: $printername went away? ($!)\n";

    print STDERR "Sent pagecount file to the printer\n" if $DEBUG;

    # Attempt to read the page count $MAX_PAGECOUNT_RETRIES times.
    for(1 .. $MAX_PAGECOUNT_RETRIES) {
        $printer->sysread($buf, $READLEN) ||
            die "$0: $printername went away? ($!)\n";
        $hist .= $buf; # Save and examine the buffer history, in case of short-reading

        print STDERR $hist if $DEBUG;

        if ($hist =~ m/%%\[ pagecount: (\d+) \]%%/ims) {
            print "$1\n";
    warn "Couldn't get pagecount after $MAX_PAGECOUNT_RETRIES tries.\n" .
         "last code was $code; buffer follows: $buf\n" .
         "history: $hist\n";

New Annotation