root / pykota / trunk / pykota / accounters / snmp.py @ 2277

Revision 2277, 10.4 kB (checked in by jerome, 19 years ago)

Fixed incorrect loop counter in pjl and snmp accounters.
Now uses USTATUS in the pjl accounter to workaround printers
which switch to 'busy' state when we request their status
(meaning they were always busy) : seems to work just fine
now on my HP2300N.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Rev Id
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota - Print Quotas for CUPS and LPRng
5#
6# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23#
24
25ITERATIONDELAY = 1.5   # 1.5 Second
26STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
27
28import sys
29import os
30import time
31
32try :
33    from pysnmp.asn1.encoding.ber.error import TypeMismatchError
34    from pysnmp.mapping.udp.error import SnmpOverUdpError
35    from pysnmp.mapping.udp.role import Manager
36    from pysnmp.proto.api import alpha
37except ImportError :
38    class Handler :
39        def __init__(self, parent, printerhostname) :
40            """Just there to raise an exception."""
41            raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/"
42else :   
43    pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1
44    pageCounterOID2 = ".1.3.6.1.2.1.43.10.2.1.5.1.1" # SNMPv2-SMI::mib-2.43.10.2.1.5.1.1
45    hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1
46    printerStatusValues = { 1 : 'other',
47                            2 : 'unknown',
48                            3 : 'idle',
49                            4 : 'printing',
50                            5 : 'warmup',
51                          }
52    hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1
53    deviceStatusValues = { 1 : 'unknown',
54                           2 : 'running',
55                           3 : 'warning',
56                           4 : 'testing',
57                           5 : 'down',
58                         } 
59    hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1
60    prtConsoleDisplayBufferTextOID = ".1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1
61                         
62    #                     
63    # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB)
64    #
65    class Handler :
66        """A class for SNMP print accounting."""
67        def __init__(self, parent, printerhostname) :
68            self.parent = parent
69            self.printerHostname = printerhostname
70            self.printerInternalPageCounter = None
71            self.printerInternalPageCounter2 = None
72            self.printerStatus = None
73            self.deviceStatus = None
74           
75        def retrieveSNMPValues(self) :   
76            """Retrieves a printer's internal page counter and status via SNMP."""
77            ver = alpha.protoVersions[alpha.protoVersionId1]
78            req = ver.Message()
79            req.apiAlphaSetCommunity('public')
80            req.apiAlphaSetPdu(ver.GetRequestPdu())
81            req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \
82                                                        (pageCounterOID2, ver.Null()), \
83                                                        (hrPrinterStatusOID, ver.Null()), \
84                                                        (hrDeviceStatusOID, ver.Null()))
85            tsp = Manager()
86            try :
87                tsp.sendAndReceive(req.berEncode(), (self.printerHostname, 161), (self.handleAnswer, req))
88            except SnmpOverUdpError, msg :   
89                self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn")
90            tsp.close()
91   
92        def handleAnswer(self, wholeMsg, notusedhere, req):
93            """Decodes and handles the SNMP answer."""
94            self.parent.filter.logdebug("SNMP message : '%s'" % repr(wholeMsg))
95            ver = alpha.protoVersions[alpha.protoVersionId1]
96            rsp = ver.Message()
97            try :
98                rsp.berDecode(wholeMsg)
99            except TypeMismatchError, msg :   
100                self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn")
101            else :
102                if req.apiAlphaMatch(rsp):
103                    errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus()
104                    if errorStatus:
105                        self.parent.filter.printInfo(_("Problem encountered while doing SNMP queries on printer %s : %s") % (self.printerHostname, errorStatus), "warn")
106                    else:
107                        self.values = []
108                        for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList():
109                            self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value)
110                        try :   
111                            # keep maximum value seen for printer's internal page counter
112                            self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0])
113                            self.printerInternalPageCounter2 = max(self.printerInternalPageCounter2, self.values[1])
114                            self.printerStatus = self.values[2]
115                            self.deviceStatus = self.values[3]
116                            self.parent.filter.logdebug("SNMP answer is decoded : PageCounters : (%s, %s)  PrinterStatus : %s  DeviceStatus : %s" % tuple(self.values))
117                        except IndexError :   
118                            self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values))
119                            pass
120                        else :   
121                            return 1
122                       
123        def waitPrinting(self) :
124            """Waits for printer status being 'printing'."""
125            firstvalue = None
126            while 1:
127                self.retrieveSNMPValues()
128                statusAsString = printerStatusValues.get(self.printerStatus)
129                if statusAsString in ('printing', 'warmup') :
130                    break
131                if self.printerInternalPageCounter is not None :   
132                    if firstvalue is None :
133                        # first time we retrieved a page counter, save it
134                        firstvalue = self.printerInternalPageCounter
135                    else :     
136                        # second time (or later)
137                        if firstvalue < self.printerInternalPageCounter :
138                            # Here we have a printer which lies :
139                            # it says it is not printing or warming up
140                            # BUT the page counter increases !!!
141                            # So we can probably quit being sure it is printing.
142                            self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
143                            break
144                self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)   
145                time.sleep(ITERATIONDELAY)
146           
147        def waitIdle(self) :
148            """Waits for printer status being 'idle'."""
149            idle_num = idle_flag = 0
150            while 1 :
151                self.retrieveSNMPValues()
152                pstatusAsString = printerStatusValues.get(self.printerStatus)
153                dstatusAsString = deviceStatusValues.get(self.deviceStatus)
154                idle_flag = 0
155                if (pstatusAsString == 'idle') or \
156                   ((pstatusAsString == 'other') and \
157                    (dstatusAsString == 'running')) :
158                    idle_flag = 1       # Standby / Powersave is considered idle
159                if idle_flag :   
160                    idle_num += 1
161                    if idle_num >= STABILIZATIONDELAY :
162                        # printer status is stable, we can exit
163                        break
164                else :   
165                    idle_num = 0
166                self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)   
167                time.sleep(ITERATIONDELAY)
168               
169        def retrieveInternalPageCounter(self) :
170            """Returns the page counter from the printer via internal SNMP handling."""
171            try :
172                if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
173                   (os.environ.get("PYKOTAACTION") != "DENY") and \
174                   (os.environ.get("PYKOTAPHASE") == "AFTER") and \
175                   self.parent.filter.jobSizeBytes :
176                    self.waitPrinting()
177                self.waitIdle()   
178            except :   
179                if self.printerInternalPageCounter is None :
180                    raise
181                else :   
182                    self.parent.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.printername), "warn")
183            return self.printerInternalPageCounter
184           
185if __name__ == "__main__" :           
186    if len(sys.argv) != 2 :   
187        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
188    else :   
189        def _(msg) :
190            return msg
191           
192        class fakeFilter :
193            def __init__(self) :
194                self.printername = "FakePrintQueue"
195                self.jobSizeBytes = 1
196               
197            def printInfo(self, msg, level="info") :
198                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
199                sys.stderr.flush()
200               
201            def logdebug(self, msg) :   
202                self.printInfo(msg, "debug")
203               
204        class fakeAccounter :       
205            def __init__(self) :
206                self.filter = fakeFilter()
207                self.protocolHandler = Handler(self, sys.argv[1])
208           
209        acc = fakeAccounter()           
210        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.