#!/usr/bin/perl -w use strict; use IO::Socket::INET; use List::Util qw(first); my $DEBUG = undef; # getpagecount.pl # 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: # http://lprng.sourceforge.net/DISTRIB/RESOURCES/DOCS/pjltkref.pdf # 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) my $PAGECOUNT_PS = q{@PJL ENTER LANGUAGE=POSTSCRIPT %!PS (%%[ pagecount: ) print statusdict /pagecount get exec % Get the page count register ( ) cvs print % Stuff it into the string ( ]%%) = % Write it out flush }; # If our PJL INFO ID matches this, we need a different code. my $PHASER_WORKAROUND = qr/\@PJL INFO ID\cM?\cJXerox Phaser /ms; my $PHASER_WORKAROUND_CODE = 10023; # 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; push @GOOD_CODES, $PHASER_WORKAROUND_CODE; } # 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"; exit; } } warn "Couldn't get pagecount after $MAX_PAGECOUNT_RETRIES tries.\n" . "last code was $code; buffer follows: $buf\n" . "history: $hist\n"; }