         Using SNMP To Get Printer Status
      Patrick Powell <papowell at lprng.com>
               16 Oct 2003

1. Overview
   The IFHP filter can use a SNMP helper program 'snmp_printer_status'
to query the printer for status information.   This program can be
modified to suit the particular printer.

The original idea for the snmp_printer_status program was by:

      John Perkins <john at cs.wisc.edu>

The code has been modified, changed, etc. etc.  I think that there
might be a comma or period left from the original,  but that
is probably all.

EXTRA BRIEF EXECUTIVE SUMMARY FOR THE PATHOLOGICALLY IMPATIENT:

  a) check to see if you have Net::SNMP installed:
      perl -MNet::SNMP -e exit
     If not, then you will have to do the
     steps in 4. Installing SNMP Tools.

  b) check to see if you can get the printer status.  Here is a
     script to do so.  The IP address used here should be replaced
     by your IP address.
     
	#!/bin/sh
	V=10.0.0.28  
	snmpwalk -Cc -v1 -c public    -M +/usr/local/share/snmp/mibs -m ALL $V .iso
	snmpwalk -Cc -v1 -c public -Of -M +/usr/local/share/snmp/mibs -m ALL $V .iso
	snmpwalk -Cc -v1 -c public -On -M +/usr/local/share/snmp/mibs -m ALL $V .iso

  c) Install IFHP as you normally would.  Now try: 
       snmp_printer_status -h 10.0.0.28

     If this works, and gets status, then you are ready.  If not, then you
     will have to find out. 

  d) In your LPRng configuration, use a socket connection.

     In your IFHP configuration, use appsocket.  This will solve a
     problem concerning connections and status reporting.  On some
     printers, when a connection is open to the printer it will
     indicate that it is 'busy'.

     lp:lp=IPADDR%port

     Use SNMP for 'sync', 'pagecount', and 'waitend'.  You may
     not get all your error reports if you use snmp for 'waitend'.
     Experiment and see what works best.

     Try this first:   Then this
     # options         # options
     appsocket         appsocket
     snmp              snmp
     sync=snmp         sync=snmp
     waitend           waitend=snmp
     pagecount=snmp    pagecount=snmp

  e) You can extract the guts of the snmp_printer_status program and
     put them into your favorite accounting script, which runs at the
     start and end of a job.

2. SNMP Basics
   The Simple Network Managment Protocol was conceived in the wild and
youthful years of the TCP/IP protocol.  It has the following properties:
  a) queries and results are sent using UDP (version 1)
  b) The 'agent' program runs on the target system and reports the
     information requested by a query.
  c) There is a standard set of 'queries' that can be used.
  d) The Mananagement Information Base (sic) (MIB) describes the
     things that can be queried.
  e) There is little to no security in the Version 1 and Version 2
     of the protocols.
  f) almost all printers use Version 1, and implement all or part
     of the Printer MIB.

3. Status Information
   The following items are usually queried.

3.1 hrDeviceStatus

HOST-RESOURCES-MIB::hrDeviceStatus.1 = INTEGER:
      unknown(1), running(2), warning(3), testing(4), down(5)
OID Text format: 
.iso.org.dod.internet.mgmt.mib-2.host
   .hrDevice.hrDeviceTable.hrDeviceEntry.hrDeviceStatus.1
OID Numberical format:
.1.3.6.1.2.1.25.3.2.1.5.1

The current operational state of the device described by this row
of the table.  A value unknown(1) indicates that the current state
of the device is unknown.  running(2) indicates that the device is
up and running and that no unusual error conditions are known.  The
warning(3) state indicates that agent has been informed of an unusual
error condition by the operational software (e.g., a disk device
driver) but that the device is still 'operational'.  An example
would be a high number of soft errors on a disk.  A value of
testing(4), indicates that the device is not available for use
because it is in the testing state.  The state of down(5) is used
only when the agent has been informed that the device is not available
for any use.

3.2 hrPrinterStatus

HOST-RESOURCES-MIB::hrPrinterStatus.1 = INTEGER:
   other(1), unknown(2), idle(3), printing(4), warmup(5)
OID Text format: 
.iso.org.dod.internet.mgmt.mib-2.host
  .hrDevice.hrPrinterTable.hrPrinterEntry.hrPrinterStatus.1 
OID Numberical format:
.1.3.6.1.2.1.25.3.5.1.1.1

The current status of this printer device.

3.3 prtMarkerLifeCount

Printer-MIB::prtMarkerLifeCount.1.1 = Counter32
.iso.org.dod.internet.mgmt.mib-2.printmib
  .prtMarker.prtMarkerTable.prtMarkerEntry.prtMarkerLifeCount.1.1
.1.3.6.1.2.1.43.10.2.1.4.1.1

This is the number of items that have been marked.  There are other fields
that tell what the unit values are,  and what this measures.  Almost
always this is the number of impressions,  not pages, that have
been made by the printer.

This is the value of interest for page counts.

3.4 prtConsoleDisplayBufferText

Printer-MIB::prtConsoleDisplayBufferText.1.1 = STRING
.iso.org.dod.internet.mgmt.mib-2.printmib.
   .prtConsoleDisplayBuffer.prtConsoleDisplayBufferTable
   .prtConsoleDisplayBufferEntry.prtConsoleDisplayBufferText.1.1
.1.3.6.1.2.1.43.16.5.1.2.1.1 = STRING

The content of a line in the logical display buffer of the operator's
console of the printer.  When a write operation occurs, normally a
critical message, to one of the LineText strings, the agent should
make that line displayable if a physical display is present.  Writing
a zero length string clears the line.  It is an implementation-specific
matter as to whether the agent allows a line to be overwritten
before it has been cleared. Printer generated strings shall be in
the localization specified by prtConsoleLocalization.  Management
Application generated strings should be localized by the Management
Application.

Briefly,  this is the status information that is usually shown on
the front panel.  By experiment, in most printers it appears to be
the same status that is returned by various PJL error reporting
facilities.

3.5 Alerts

These monitor conditions in the printer.  It appears that some
printers do not support these conditions,  and only report
errors using the prtConsoleDisplayBufferText or some other
proprietary OID.

3.5.1 prtAlertSeverityLevel

Printer-MIB::prtAlertSeverityLevel.1.1 = INTEGER
  other(1), critical(3), warning(4), warningBinaryChangeEvent(5)
.iso.org.dod.internet.mgmt.mib-2.printmib.prtAlert
  .prtAlertTable.prtAlertEntry.prtAlertSeverityLevel.1.1
.1.3.6.1.2.1.43.18.1.1.2.1.1 = INTEGER: warning(4)

The level of severity of this alert table entry.  The printer
determines the severity level assigned to each entry in the table.
A critical alert is binary by nature and definition.  A warning is
defined to be a non-critical alert. The original and most common
warning is unary. The binary warning was added later and given a
more distinguished name.

3.5.2 prtAlertGroup

Printer-MIB::prtAlertGroup.1.3 = INTEGER
	other(1), hostResourcesMIBStorageTable(3),
	hostResourcesMIBDeviceTable(4), generalPrinter(5), cover(6),
	localization(7), input(8), output(9), marker(10),
	markerSupplies(11), markerColorant(12), mediaPath(13),
	channel(14), interpreter(15), consoleDisplayBuffer(16),
	consoleLights(17), alert(18), finDevice(30), finSupply(31),
	finSupplyMediaInput(32), finAttributeTable(33)
.iso.org.dod.internet.mgmt.mib-2.printmib.prtAlert
  .prtAlertTable.prtAlertEntry.prtAlertGroup.1.3 = INTEGER
.1.3.6.1.2.1.43.18.1.1.4.1.3 = INTEGER

The type of sub-unit within the printer model that this alert is
related.  Input, output, and markers are examples of printer model
groups, i.e., examples of types of sub-units.  Wherever possible,
these enumerations match the sub-identifier that identifies the
relevant table in the printer MIB.

NOTE: Alert type codes have been added for the host resources MIB
storage table and device table. These additional types are for
situations in which the printer's storage and device objects must
generate alerts (and possibly traps for critical alerts).

3.5.3 prtAlertDescription

Printer-MIB::prtAlertDescription.1.1 = STRING
.iso.org.dod.internet.mgmt.mib-2.printmib.prtAlert.prtAlertTable
     .prtAlertEntry.prtAlertDescription.1.1 = STRING
.1.3.6.1.2.1.43.18.1.1.8.1.1 = STRING

A description of this alert entry in the localization specified
by prtGeneralCurrentLocalization.  The description is provided by
the printer to further elaborate on the enumerated alert or provide
information in the case where the code is classified as 'other' or
'unknown'.  The printer is required to return a description string
but the string may be a null string.

3.6 prtMarkerPowerOnCount

Printer-MIB::prtMarkerPowerOnCount.1.1 = Counter32
.iso.org.dod.internet.mgmt.mib-2.printmib.prtMarker
   .prtMarkerTable.prtMarkerEntry.prtMarkerPowerOnCount.1.1
.1.3.6.1.2.1.43.10.2.1.5.1.1 = Counter32

The count of the number of units of measure counted since the
equipment was most recently powered on using units of measure as
specified by prtMarkerCounterUnit.

4. Installing SNMP Tools

If you are using FreeBSD and have the ports packages, you can simply do:

  cd  /usr/ports/net/p5-Net-SNMP
  make
  make install   

Check to see if you have 'snmpwalk' available on your system:
  snmpwalk

If not, get the NET-SNMP utilities from Sourceforge:
 http://sourceforge.net/projects/net-snmp/

Follow the simple instructions and install the programs.
I recommend using the default locations and install
the various libraries and tools in /usr/local/...

Now check to make sure that snmpwalk can access your printer:
    #!/bin/sh
    V=10.0.0.28
    snmpwalk -Cc -v1 -c public    -M +/usr/local/share/snmp/mibs -m ALL $V .iso
    snmpwalk -Cc -v1 -c public -Of -M +/usr/local/share/snmp/mibs -m ALL $V .iso
    snmpwalk -Cc -v1 -c public -On -M +/usr/local/share/snmp/mibs -m ALL $V .iso

Now check to see if you have the Net::SNMP module installed:

   perl -MNet::SNMP -e exit

If not, then you will have to install it.  If you are brave, as root you can do:

     perl -MCPAN -e "install Net::SNMP"

If not, then you will have to download all of the reuqired modules and install
them one by one.  I really recommend using the above approach.

You can get a set of printer MIBS from the LPRng Web or FTP Site:

ftp://ftp.lprng.com/pub/LPRng/RESOURCES/SNMP_MIBS/mibs.tar.gz

Untar these and put them into the default location used by the SNMP software.
Candidates include:
  - /usr/local/share/snmp/mibs
  - /usr/share/snmp/mibs

5. snmp_printer_status

The snmp_printer_status command is really just a cut down version
of the snmpwalk command.  It queries the printer for the OID values
shown above and formats the status information.

use: snmp_printer_status [-vps][-t interval][-h ipaddr][-m model][-f config]
        -v          - verbose mode
        -p          - no pagecount
        -s          - show poweron pagecount
        -t interval  - interval in secs between queries (default 1)
        -h ipaddr   - printer ip address or DNS name
        -m model    - model information
        -f config - configuration file

The required parameters are:
   -h ipaddr or DNS name 
      This can have the format:  ip%port or hostname%port
      to be compatible with LPRng 

Example of use:

h110: {785} % snmp_printer_status -h 10.0.0.31
PRINTERSTATUS= idle
DEVICESTATUS= unknown
STATUS= READY
PAGECOUNT= 12532
                       << NEWLINE entered
PRINTERSTATUS= idle
DEVICESTATUS= unknown
STATUS= READY
PAGECOUNT= 12532

The PRINTERSTATUS, DEVICESTATUS, STATUS, and PAGECOUNT are simply the
values report by the Printer Agent.  The snmp_printer_status periodically
queries the printer, checking to see if any have changed.  If they have,
the new values are printed out.  When the program reads a line of input
it reads the MIB values and dumps them out again.

If the prAlert MIB OID's are supported, you can monitor these as well:

h110: {787} % snmp_printer_status -h 10.0.0.28
STATUS= level: warning group: input message: Input Tray Low
STATUS= level: warning group: input message: Input Tray Low
STATUS= level: warningBinaryChangeEvent group: generalPrinter message: Power Saver
PRINTERSTATUS= other
DEVICESTATUS= unknown
STATUS= Power Save Mode
PAGECOUNT= 93162

The snmp_printer_status.conf file has a set of configuration values for
the snmp_printer_status program:

[snmp_params]
community="public"
timeout="2" 
retries="2" >

[ printerdata default]
alert_oid =".1.3.6.1.2.1.43.18.1.1"
prtAlertSeverityLevel = ".1.3.6.1.2.1.43.18.1.1.2.1"
prtAlertDescription =   ".1.3.6.1.2.1.43.18.1.1.8.1"
prtAlertGroup =         ".1.3.6.1.2.1.43.18.1.1.4.1"
pagecount_oid=".1.3.6.1.2.1.43.10.2.1.4.1" 
poweroncount_oid=".1.3.6.1.2.1.43.10.2.1.5.1.1"
devicestatus_oid=".1.3.6.1.2.1.25.3.2.1.5.1"
printerstatus_oid=".1.3.6.1.2.1.25.3.5.1.1.1"
# This should report console or other status
status_oid=".1.3.6.1.2.1.43.16.5.1.2.1.1" 
[ status devicestatus_oid ]
other="1"  unknown="2"  idle="3"
printing="4"  warmup="5"
[ status printerstastus_oid]
other="1" unknown="2" idle="3"
printing="4" warmup="5"></printerstatus>
[ status alertseveritylevel_oid]
other="1" critical="3" warning="4"
warningBinaryChangeEvent="5"></alertseveritylevel>
[ status alertgroup_oid ]
other="1" hostResourcesMIBStorageTable="3"
hostResourcesMIBDeviceTable="4" generalPrinter="5" cover="6"
localization="7" input="8" output="9" marker="10"
markerSupplies="11" markerColorant="12" mediaPath="13"
channel="14" interpreter="15" consoleDisplayBuffer="16"
consoleLights="17" alert="18" finDevice="30"

The snmp_parms section sets parameter values for the SNMP queries.
The <printerdata> section defines the OID values to be used for
fetching status.  The name parameter set the name of the various
entries.

The other entries are used to decode the SNMP return values into
user strings.

7. IFHP and snmp_printer_status

The use of snmp for getting printer status has been merged into
the ifhp baseline distribution.
Here are the changes to the ifhp.conf file:

    #--- START SNMP monitoring
    
    # if you want to use SNMP monitoring then use the snmp_printer_status
    # program.  This program will report the status of a printer
    # by polling it using SNMP.  Status will be read from both the
    # back channel (i.e. - connection to printer) and from the SNMP
    # monitoring program.
    
    snmp_monitor@
    snmp_program=/usr/local/libexec/filters/snmp_printer_status -h "\%s{dev}" -m "\%s{snmp_model}"
    
    # the snmp_printer_status.conf file has entries to specify the
    # SNMP Object Identifiers (OIDs) for variuos printers
    # The default is to use the standard Printer MIB entries for status.
    # Again, you should look at the code and modify the entries for your
    # printer.
    snmp_model=default
    
    # when we get status, we look for the following words in the
    # status to indicate that the printer is ready and/or idle.
    # Spaces in the status are replaced by underscores if necessary
    snmp_sync_status=ready power_save_mode
    snmp_end_status=ready idle
    
    #--- END SNMP monitoring

    # ... sync
    # use SNMP status for idle condition monitoring
    sync=snmp

    # ... waitend
    # use SNMP status for end of job detection
    sync=snmp

    # ... Pagecount
    # use SNMP pagecount status for pagecount monitoring
    pagecount=snmp
    # when we get status, we look for the following words in the
    # status to indicate that the printer is ready and/or idle.
    # Spaces in the status are replaced by underscores if necessary
    snmp_sync_status=ready power_save_mode
    snmp_end_status=ready idle


When you use SNMP monitoring you can use it to do SYNC, PAGECOUNT,
or WAITEND as the status will be determined by the
snmp_printer_status program.

SYNC Condition:  STATUS= value in the snmp_sync_status list
END  Condition:  STATUS= value in the snmp_end_status list

The PAGECOUNT value is reoprted only when conditions are appropriate
to an end of job:

No error condition and:
  DEVICESTATUS != 'down' and DEVICESTATUS != 'testing'
  and PRINTERSTATUS != 'printing' and PRINTERSTATUS != 'unknown'
The ifhp filter will use snmp_printer_status to get the current
printer status.  The following set of options has been tried and
seems to work:


8. Testing ifhp and snmp_printer_status

Use the following test script.  Edit it and set the appropriate
values.

	#!/bin/sh
	# test ifhp + snmp
    # location of ifhp
	ifhp=/usr/local/libexec/filters/ifhp
	out=/tmp/out
	if [ -f "$out" ] ; then cp /dev/null $out ; fi
	# replace with your printers IP address
	# and port number
	out=10.0.0.31%9100
	debug=4  # lots of
	debug=1 # simple debugging
	debug=0 # no debugging
	config=/usr/local/etc/ifhp.conf
	file=/usr/local/libexec/filters/UTILS/one.ps
    # set the option you want
	options=snmp_monitor,appsocket,close_connection,pjl_console@
	options=snmp_monitor,appsocket,close_connection
	options=snmp_monitor,appsocket
	options=snmp_monitor
	$ifhp "-Tdev=$out,trace,config=$config,debug=$debug,model=$model,$options" <$file 2>&1 | tee /tmp/log

Here is a sample of the output to an HP printer using options=snmp_monitor:

h110: {47} % sh /tmp/sendhp.sh
ifhp 15:42:47.203 [60711] main: using model ''
ifhp 15:42:47.211 [60711] Check_pagecount: pagecount using 'snmp'
ifhp 15:42:47.216 [60711] Process_job: setting up printer
ifhp 15:42:47.226 [60711] Do_sync: getting sync using 'snmp'
ifhp 15:42:47.536 [60711] Check_device_status: printerstatus = 'idle'
ifhp 15:42:47.569 [60711] Check_device_status: devicestatus = 'unknown'
ifhp 15:42:47.604 [60711] Check_device_status: status = 'READY'
ifhp 15:42:47.604 [60711] Do_sync: sync done
ifhp 15:42:47.639 [60711] Do_pagecount: pagecounter 12533 after 1 attempts
ifhp 15:42:47.639 [60711] Do_accounting: pagecounter 12533
ifhp 15:42:47.640 [60711] Process_job: sending job file
ifhp 15:42:47.640 [60711] Send_job: starting transfer
ifhp 15:42:47.641 [60711] Send_job: initial job type 'POSTSCRIPT'
ifhp 15:42:47.641 [60711] Send_job: decoded job type 'POSTSCRIPT'
ifhp 15:42:47.641 [60711] Send_job: foomatic enabled and no PPD file specified
ifhp 15:42:47.641 [60711] Send_job: job type 'POSTSCRIPT'
ifhp 15:42:47.642 [60711] Send_job: transferring 145 bytes
ifhp 15:42:47.642 [60711] Send_job: 100 percent done
ifhp 15:42:47.642 [60711] Process_job: sent job file
ifhp 15:42:47.642 [60711] Do_waitend: getting end using 'snmp'
ifhp 15:42:48.331 [60711] Do_waitend: end of job detected
ifhp 15:42:49.099 [60711] Check_device_status: id = '"HP LaserJet 2200"'
ifhp 15:42:49.886 [60711] Check_device_status: printerstatus = 'printing'
ifhp 15:42:49.954 [60711] Check_device_status: status = 'PRINTING DOCUMENT'
ifhp 15:43:05.846 [60711] Check_device_status: printerstatus = 'idle'
ifhp 15:43:05.948 [60711] Do_pagecount: pagecounter 12534 after 1 attempts
ifhp 15:43:05.949 [60711] Do_accounting: pagecounter 12534, pages 1
ifhp 15:43:05.949 [60711] Process_job: done


Here is a sample of output to another printer:

fhp 17:17:35.690 [61562] main: using model ''
ifhp 17:17:36.303 [61562] Check_pagecount: pagecount using 'snmp'
ifhp 17:17:36.313 [61562] Process_job: setting up printer
ifhp 17:17:36.323 [61562] Do_sync: getting sync using 'snmp'
ifhp 17:17:36.571 [61562] Check_device_status: devicedescr = 'Sharp AR-507/S507'
ifhp 17:17:36.607 [61562] Check_device_status: printername = 'Sharp'
ifhp 17:17:36.981 [61562] Check_device_status: status = 'level: warning group: input message: Input Tray Low'
ifhp 17:17:36.981 [61562] Check_device_status: status = 'level: warning group: input message: Input Tray Low'
ifhp 17:17:36.982 [61562] Check_device_status: status = 'level: warningBinaryChangeEvent group: generalPrinter message: Power Saver'
ifhp 17:17:38.379 [61562] Check_device_status: printerstatus = 'other'
ifhp 17:17:38.414 [61562] Check_device_status: devicestatus = 'unknown'
ifhp 17:17:38.445 [61562] Check_device_status: status = 'Power Save Mode'
ifhp 17:17:38.447 [61562] Do_sync: sync done
ifhp 17:17:38.475 [61562] Do_pagecount: pagecounter 93168 after 1 attempts
ifhp 17:17:38.475 [61562] Do_accounting: pagecounter 93168
ifhp 17:17:38.476 [61562] Process_job: sending job file
ifhp 17:17:38.476 [61562] Send_job: starting transfer
ifhp 17:17:38.477 [61562] Send_job: initial job type 'POSTSCRIPT'
ifhp 17:17:38.477 [61562] Send_job: decoded job type 'POSTSCRIPT'
ifhp 17:17:38.477 [61562] Send_job: foomatic enabled and no PPD file specified
ifhp 17:17:38.477 [61562] Send_job: job type 'POSTSCRIPT'
ifhp 17:17:38.478 [61562] Send_job: transferring 145 bytes
ifhp 17:17:38.478 [61562] Send_job: 100 percent done
ifhp 17:17:38.478 [61562] Process_job: sent job file
ifhp 17:17:38.479 [61562] Do_waitend: getting end using 'snmp'
ifhp 17:17:41.521 [61562] Check_device_status: status = 'level: warningBinaryChangeEvent group: generalPrinter message: Power Saver CLEARED'
ifhp 17:17:42.807 [61562] Check_device_status: printerstatus = 'printing'
ifhp 17:17:42.863 [61562] Check_device_status: status = 'Printer:  Receiving data'
ifhp 17:18:35.473 [61562] Check_device_status: status = 'Printer:  Printing in progress'


9. Problem Printers

The main problem encountered using SNMP for status is that some printers
do not seem to either update their SNMP status in sync with the printing
activity.  The PJL reverse channel may report printing completed
while the SNMP agent will still report it as active.  In addition,
the agent is 'slow' at startup.  For example,  the job may actually
be transferred to the printer before the agent has seen any status change.

To deal with this, various experiments have been tried.  The most
successful seems to be to use 'appsocket' printing, ie, to have the
ifhp process open and close the TCP/IP connection.  This has the
side effect of forcing an end of connection action that seems to
make most printer SNMP agents update the status.  Also, there is a
slight delay in the close operation that may have the same effect.

Another issue that has surfaced is that some printers have defective
TCP/IP stacks that cannot handle a shutdown() on a TCP/IP connection.
These require the use of the (previously undocumented) close_connect flag.

Finally, there are printers that do not correctly report 'end of printing'
activity using PJL.  These may be better handled using the 'appsocket'
and SNMP approach.

8. LPRng and IFHP 

The following printcap options seem to yeild the best results.

lp:
  :lp=10.0.0.14%9100      # You must use socket protocol.
  :...

# this work for many printers that have 'sane' network connections
  :ifhp=snmp_monitor
# For problem printers that report End of Job early,
# you may need to do this to force an 'end of job' condition
  :ifhp=snmp_monitor,appsocket
# some printers will require that you close the connection
# as their TCP/IP stacks are broken and they cannot handle
# a shutdown
  :ifhp=snmp_monitor,appsocket,close_connection

  snmp_monitor - use the snmp to get the status
  appsocket    - you need to force the connection open and closed.
                 This appears to solve some issues with jobs not
                 completing and the SNMP status indicates 'printing'
  close_connection - this may be necessary to force the end of job
                condition on some printers.

 Experimental Results:

    :ifhp=snmp_monitor
       worked on 3 different HP printers
    :ifhp=snmp_monitor,appsocket
       worked on printers that required appsocket.  It also
       seemed to report end of job much better.
    :ifhp=snmp_monitor,appsocket,close_connection
       worked on printers that required appsocket AND required
       the connection to be closed for end of job to be detected.
       Several MOPIERS fell into this group,  as the copier/printer
       would only change mode from copy to printing once it had the
       entire job,  or sufficient number of pages to cause it to
       print.
