#!/usr/common/bin/perl -w
# This script contains subroutines to check printer status
############################################################
# 10/11/99 rcc - Add "printer_idle"; Remove "service request" as a "down"
# 09/24/99 rcc - Add "get_pagecount"
# 08/01/99 rcc - Convert NPStat.pl to Perl 5 module npstatlib.pm
# 04/09/99 rcc - Reduce SNMP retries from 5 to 2
# 10/01/98 rcc
############################################################

package npstatlib;

# "use diagnostics" should be commented out for production environment
#use diagnostics;
use strict;

use vars qw{ @ISA @EXPORT };
require Exporter;
@ISA = qw{ Exporter };
@EXPORT = qw{
    get_status
    err2str
    printer_ok
    get_printerlist
    get_printerinfo
    get_allprinterinfo
    get_pagecount
};

# exported
use vars qw{
};

# non-exported but global to the package:
use vars qw{
    $PSTAT_SERVICE_REQ
    $PSTAT_OFFLINE
    $PSTAT_PAPER_JAM
    $PSTAT_DOOR_OPEN
    $PSTAT_TONER_OUT
    $PSTAT_TONER_LOW
    $PSTAT_PAPER_OUT
    $PSTAT_PAPER_LOW
    $PSTAT_IDLE
    $PSTAT_PRINTING
    $PSTAT_WARMING_UP
    $PSTAT_UNREACHABLE
    $PSTAT_MAX
    %statmsg
    %prtstatmsg
};

# Loads modules from source directory if executed in source directory
use lib qw(. /usr/local/netprint/lib);
use npparams;
use SNMP_mib;
use SNMP_Session;
use BER;

# Note:  The bit definitions for the first 8 of the items below are taken
# from the description of the values for hrPrinterDetectedErrorState in
# RFC1759.
BEGIN {
    $PSTAT_SERVICE_REQ = 1;
    $PSTAT_OFFLINE = 2;
    $PSTAT_PAPER_JAM = 4;
    $PSTAT_DOOR_OPEN = 8;
    $PSTAT_TONER_OUT = 16;
    $PSTAT_TONER_LOW = 32;
    $PSTAT_PAPER_OUT = 64;
    $PSTAT_PAPER_LOW = 128;
    $PSTAT_IDLE = 256;
    $PSTAT_PRINTING = 512;
    $PSTAT_WARMING_UP = 1024;
    $PSTAT_UNREACHABLE = 2048;
    $PSTAT_MAX = 2048;

    %statmsg =
	(
	 $PSTAT_SERVICE_REQ, 'Service Requested',
	 $PSTAT_OFFLINE, 'Offline',
	 $PSTAT_PAPER_JAM, 'Paper Jam',
	 $PSTAT_DOOR_OPEN, 'Door Open',
	 $PSTAT_TONER_OUT, 'Toner Out',
	 $PSTAT_TONER_LOW, 'Toner Low',
	 $PSTAT_PAPER_OUT, 'Paper Out',
	 $PSTAT_PAPER_LOW, 'Paper Low',
	 $PSTAT_IDLE, 'Idle',
	 $PSTAT_PRINTING, 'Printing',
	 $PSTAT_WARMING_UP, 'Warming Up',
	 $PSTAT_UNREACHABLE, 'DISCONNECTED, OFFLINE, OR NOT RESPONDING',
	 );

    %prtstatmsg =
	(
	 3, $PSTAT_IDLE,
	 4, $PSTAT_PRINTING,
	 5, $PSTAT_WARMING_UP,
	 );
}


########################################################
#
# get_status($printer)
#
# Uses SNMP to find the status and returns an errorcode
# which can be deciphered in err2str, which will return an array of
# strings specifying the error(s) occuring.

sub get_status {
    my($printer) = @_;
    my(%pinfo);
    my($ip, $community, $session, $type, $devstat, $prtstat, $error);

    if ( ! defined(%pinfo = get_printerinfo($printer)) ) {
	return(undef);
    }
    if ( $pinfo{VIRTUAL} =~ /^[yY]/ ) {
	return($PSTAT_IDLE);
    }
    $ip = $pinfo{IP};
    $community = 'public';

    # The following line can be commented out for debugging purposes.
    $SNMP_Session::suppress_warnings = 1;
    snmp_initmib();
    if ( ! ($session = SNMP_Session->open ($ip, $community, 161)) ) {
        warn "Couldn't open SNMP session to $printer (at $ip): $SNMP_Session::errmsg";
        return($PSTAT_UNREACHABLE);
    }
    $session->set_retries(2);
    ($type, $devstat, $prtstat, $error) = snmp_get($session, qw(sysDescr.0 hrDeviceStatus.1 hrPrinterStatus.1 hrPrinterDetectedErrorState.1));
    if ( ! defined($type) ) {
	return($PSTAT_UNREACHABLE);
    }

    my($tmp) = unpack("C", $error);
    if ( defined($prtstatmsg{$prtstat}) ) {
	$tmp |= $prtstatmsg{$prtstat};
    }
    return($tmp);
}

#########################################################
# err2str ($code)
#
# Given an errorcode, this sub will convert the
# code to an array of strings, each ending in a newline.

sub err2str {
    my($code) = @_;
    my(@msgs);
    my($i);

    @msgs = ();
    for ( $i = 1; $i <= $PSTAT_MAX; $i <<= 1 ) {
	if ( $i & $code ) {
	    push(@msgs, $statmsg{$i});
	}
    }
    return(@msgs);
}


#########################################################
# printer_ok($code)
#
# Given a printer status code, return TRUE for printer OK, and FALSE
# for printer not functioning.

sub printer_ok {
    my($code) = @_;
    return( ! ($code & ($PSTAT_OFFLINE | $PSTAT_PAPER_JAM | $PSTAT_DOOR_OPEN | $PSTAT_TONER_LOW | $PSTAT_TONER_OUT | $PSTAT_PAPER_OUT | $PSTAT_UNREACHABLE)) );
}


#########################################################
# printer_idle($code)
#
# Given a printer status code, return TRUE for printer idle, and FALSE
# for any other printer status.

sub printer_idle {
    my($code) = @_;
    return( $code == $PSTAT_IDLE );
}


#########################################################
# get_printerlist
# get_printerinfo
# get_allprinterinfo
#
# More convenient and rigorous versions of get_prt_cfg.
#  @list = get_printerlist()        places a list of all printers in $list.
#  %info = get_printerinfo($pname)  returns a hash of the printer info for
#                                   $printer.
#  %info = get_allprinterinfo()    returns a hash of hashes containing all
#                                   the printer info in the database
# 
# All routines return 'undef' for error.

# global variables (local to this package)
my(%printerinfo);
my($pi_timestamp);

sub get_printerlist {
    my(%pinfo);

    if ( ! defined(%pinfo = get_allprinterinfo()) ) {
	return(undef);
    }
    return(sort(keys(%pinfo)));
}

sub get_printerinfo {
    my($printer);
    ($printer) = @_;
    my(%pinfo);

    if ( ! defined(%pinfo = get_allprinterinfo()) ) {
	return(undef);
    }
    return(%{$pinfo{$printer}});
}

sub get_allprinterinfo {
    my($mtime, $l, $tmp, @items, $item, $printer);

    $mtime = (stat($PRTCFG))[9];
#    print "file = $PRTCFG;  timestamp = $pi_timestamp;  mtime = $mtime\n";
    if ( $pi_timestamp != $mtime ) {
#	print "Reread PRTCFG\n";
	%printerinfo = ();
	if ( ! open(CFGFILE, $PRTCFG) ) {
	    print "Unable to open $PRTCFG $!\n";
	    return(undef);
	}
	while ( defined($l = <CFGFILE>) ) {
	    chomp;
	    next if $l =~ /^#|^$/;
	    @items = split(/::/, $l);
	    $tmp = $items[0];
	    if ( ! ($tmp =~ /^NAME=(.+)$/) ) {
		print "Bad NAME entry in $PRTCFG\n";
		next;
	    }
	    $printer = $1;
	    foreach $item ( @items ) {
		if ( ! ($item =~ /^(.+)=(.+)$/) ) {
		    print "Bad parameter entry in $PRTCFG\n";
		    next;
		}
		$printerinfo{$printer}{$1} = $2;
	    }
	}
	close(CFGFILE);
	$pi_timestamp = $mtime;
    }
    return(%printerinfo);
}


#########################################################
sub get_pagecount {
    my($printer) = @_;
    my(%pinfo);
    my(@snmpvals);
    my($session);

    %pinfo = get_printerinfo($printer);

    # The following line can be commented out for debugging purposes.
    $SNMP_Session::suppress_warnings = 1;
    snmp_initmib();

    unless ( $session = SNMP_Session->open($pinfo{IP}, 'public', 161) ) {
	return(-1);
    }
    @snmpvals = snmp_get($session, qw(prtMarkerLifeCount.1.1));
    unless ( $#snmpvals >= 0 ) {
        $session->close();
        return(-1);             # Will come here if 'noSuchName' error!!
    }
    $session->close();
    return($snmpvals[0]);
}

1;
