Changeset 2205

Show
Ignore:
Timestamp:
04/13/05 00:06:49 (19 years ago)
Author:
jerome
Message:

Split hardware.py into a generic hardware.py and two protocol handler
submodules.

Location:
pykota/trunk/pykota/accounters
Files:
2 added
1 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/pykota/accounters/hardware.py

    r2203 r2205  
    2424 
    2525import os 
    26 import socket 
    27 import time 
    2826import signal 
    2927import popen2 
    3028 
    3129from pykota.accounter import AccounterBase, PyKotaAccounterError 
     30from pykota.accounters import snmp, pjl 
    3231 
    33 ITERATIONDELAY = 1.0   # 1 Second 
    34 STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable 
    35  
    36 try : 
    37     from pysnmp.asn1.encoding.ber.error import TypeMismatchError 
    38     from pysnmp.mapping.udp.error import SnmpOverUdpError 
    39     from pysnmp.mapping.udp.role import Manager 
    40     from pysnmp.proto.api import alpha 
    41 except ImportError : 
    42     hasSNMP = 0 
    43 else :     
    44     hasSNMP = 1 
    45     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 
    46     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 
    47     hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1 
    48     printerStatusValues = { 1 : 'other', 
    49                             2 : 'unknown', 
    50                             3 : 'idle', 
    51                             4 : 'printing', 
    52                             5 : 'warmup', 
    53                           } 
    54     hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1 
    55     deviceStatusValues = { 1 : 'unknown', 
    56                            2 : 'running', 
    57                            3 : 'warning', 
    58                            4 : 'testing', 
    59                            5 : 'down', 
    60                          }   
    61     hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 
    62     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 
    63                            
    64     #                       
    65     # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) 
    66     # 
    67     class SNMPAccounter : 
    68         """A class for SNMP print accounting.""" 
    69         def __init__(self, parent, printerhostname) : 
    70             self.parent = parent 
    71             self.printerHostname = printerhostname 
    72             self.printerInternalPageCounter = None 
    73             self.printerInternalPageCounter2 = None 
    74             self.printerStatus = None 
    75             self.deviceStatus = None 
    76              
    77         def retrieveSNMPValues(self) :     
    78             """Retrieves a printer's internal page counter and status via SNMP.""" 
    79             ver = alpha.protoVersions[alpha.protoVersionId1] 
    80             req = ver.Message() 
    81             req.apiAlphaSetCommunity('public') 
    82             req.apiAlphaSetPdu(ver.GetRequestPdu()) 
    83             req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \ 
    84                                                         (pageCounterOID2, ver.Null()), \ 
    85                                                         (hrPrinterStatusOID, ver.Null()), \ 
    86                                                         (hrDeviceStatusOID, ver.Null())) 
    87             tsp = Manager() 
    88             try : 
    89                 tsp.sendAndReceive(req.berEncode(), (self.printerHostname, 161), (self.handleAnswer, req)) 
    90             except SnmpOverUdpError, msg :     
    91                 self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn") 
    92             tsp.close() 
    93      
    94         def handleAnswer(self, wholeMsg, notusedhere, req): 
    95             """Decodes and handles the SNMP answer.""" 
    96             self.parent.filter.logdebug("SNMP message : '%s'" % repr(wholeMsg)) 
    97             ver = alpha.protoVersions[alpha.protoVersionId1] 
    98             rsp = ver.Message() 
    99             try : 
    100                 rsp.berDecode(wholeMsg) 
    101             except TypeMismatchError, msg :     
    102                 self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn") 
    103             else : 
    104                 if req.apiAlphaMatch(rsp): 
    105                     errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus() 
    106                     if errorStatus: 
    107                         self.parent.filter.printInfo(_("Problem encountered while doing SNMP queries on printer %s : %s") % (self.printerHostname, errorStatus), "warn") 
    108                     else: 
    109                         self.values = [] 
    110                         for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList(): 
    111                             self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value) 
    112                         try :     
    113                             # keep maximum value seen for printer's internal page counter 
    114                             self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0]) 
    115                             self.printerInternalPageCounter2 = max(self.printerInternalPageCounter2, self.values[1]) 
    116                             self.printerStatus = self.values[2] 
    117                             self.deviceStatus = self.values[3] 
    118                             self.parent.filter.logdebug("SNMP answer is decoded : PageCounters : (%s, %s)  PrinterStatus : %s  DeviceStatus : %s" % tuple(self.values)) 
    119                         except IndexError :     
    120                             self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values)) 
    121                             pass 
    122                         else :     
    123                             return 1 
    124                          
    125         def waitPrinting(self) : 
    126             """Waits for printer status being 'printing'.""" 
    127             firstvalue = None 
    128             while 1: 
    129                 self.retrieveSNMPValues() 
    130                 statusAsString = printerStatusValues.get(self.printerStatus) 
    131                 if statusAsString in ('printing', 'warmup') : 
    132                     break 
    133                 if self.printerInternalPageCounter is not None :     
    134                     if firstvalue is None : 
    135                         # first time we retrieved a page counter, save it 
    136                         firstvalue = self.printerInternalPageCounter 
    137                     else :      
    138                         # second time (or later) 
    139                         if firstvalue < self.printerInternalPageCounter : 
    140                             # Here we have a printer which lies : 
    141                             # it says it is not printing or warming up 
    142                             # BUT the page counter increases !!! 
    143                             # So we can probably quit being sure it is printing. 
    144                             self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn") 
    145                             break 
    146                 self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)     
    147                 time.sleep(ITERATIONDELAY) 
    148              
    149         def waitIdle(self) : 
    150             """Waits for printer status being 'idle'.""" 
    151             idle_num = idle_flag = 0 
    152             while 1 : 
    153                 self.retrieveSNMPValues() 
    154                 pstatusAsString = printerStatusValues.get(self.printerStatus) 
    155                 dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
    156                 idle_flag = 0 
    157                 if (pstatusAsString == 'idle') or \ 
    158                    ((pstatusAsString == 'other') and \ 
    159                     (dstatusAsString == 'running')) : 
    160                     idle_flag = 1       # Standby / Powersave is considered idle 
    161                 if idle_flag :     
    162                     idle_num += 1 
    163                     if idle_num > STABILIZATIONDELAY : 
    164                         # printer status is stable, we can exit 
    165                         break 
    166                 else :     
    167                     idle_num = 0 
    168                 self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)     
    169                 time.sleep(ITERATIONDELAY) 
    170                  
    171 pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X" 
    172 pjlStatusValues = { 
    173                     "10000" : "Powersave Mode", 
    174                     "10001" : "Ready Online", 
    175                     "10002" : "Ready Offline", 
    176                     "10003" : "Warming Up", 
    177                     "10004" : "Self Test", 
    178                     "10005" : "Reset", 
    179                     "10023" : "Printing", 
    180                     "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!! 
    181                     "40000" : "Sleep Mode",             # Standby 
    182                   } 
    183 class PJLAccounter : 
    184     """A class for PJL print accounting.""" 
    185     def __init__(self, parent, printerhostname) : 
    186         self.parent = parent 
    187         self.printerHostname = printerhostname 
    188         self.printerInternalPageCounter = self.printerStatus = None 
    189         self.timedout = 0 
    190          
    191     def alarmHandler(self, signum, frame) :     
    192         """Query has timedout, handle this.""" 
    193         self.timedout = 1 
    194         raise IOError, "Waiting for PJL answer timed out. Please try again later." 
    195          
    196     def retrievePJLValues(self) :     
    197         """Retrieves a printer's internal page counter and status via PJL.""" 
    198         port = 9100 
    199         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    200         try : 
    201             sock.connect((self.printerHostname, port)) 
    202         except socket.error, msg : 
    203             self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, port, msg), "warn") 
    204         else : 
    205             try : 
    206                 sock.send(pjlMessage) 
    207             except socket.error, msg : 
    208                 self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn") 
    209             else :     
    210                 actualpagecount = self.printerStatus = None 
    211                 self.timedout = 0 
    212                 while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) : 
    213                     signal.signal(signal.SIGALRM, self.alarmHandler) 
    214                     signal.alarm(3) 
    215                     try : 
    216                         answer = sock.recv(1024) 
    217                     except IOError, msg :     
    218                         break   # our alarm handler was launched, probably 
    219                     else :     
    220                         readnext = 0 
    221                         for line in [l.strip() for l in answer.split()] :  
    222                             if line.startswith("CODE=") : 
    223                                 self.printerStatus = line.split("=")[1] 
    224                             elif line.startswith("PAGECOUNT") :     
    225                                 readnext = 1 # page counter is on next line 
    226                             elif readnext :     
    227                                 actualpagecount = int(line.strip()) 
    228                                 readnext = 0 
    229                     signal.alarm(0) 
    230                 self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter) 
    231         sock.close() 
    232          
    233     def waitPrinting(self) : 
    234         """Waits for printer status being 'printing'.""" 
    235         firstvalue = None 
    236         while 1: 
    237             self.retrievePJLValues() 
    238             if self.printerStatus in ('10023', '10003') : 
    239                 break 
    240             if self.printerInternalPageCounter is not None :     
    241                 if firstvalue is None : 
    242                     # first time we retrieved a page counter, save it 
    243                     firstvalue = self.printerInternalPageCounter 
    244                 else :      
    245                     # second time (or later) 
    246                     if firstvalue < self.printerInternalPageCounter : 
    247                         # Here we have a printer which lies : 
    248                         # it says it is not printing or warming up 
    249                         # BUT the page counter increases !!! 
    250                         # So we can probably quit being sure it is printing. 
    251                         self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn") 
    252                         break 
    253             self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername) 
    254             time.sleep(ITERATIONDELAY) 
    255          
    256     def waitIdle(self) : 
    257         """Waits for printer status being 'idle'.""" 
    258         idle_num = idle_flag = 0 
    259         while 1 : 
    260             self.retrievePJLValues() 
    261             idle_flag = 0 
    262             if self.printerStatus in ('10000', '10001', '35078', '40000') : 
    263                 idle_flag = 1 
    264             if idle_flag :     
    265                 idle_num += 1 
    266                 if idle_num > STABILIZATIONDELAY : 
    267                     # printer status is stable, we can exit 
    268                     break 
    269             else :     
    270                 idle_num = 0 
    271             self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername) 
    272             time.sleep(ITERATIONDELAY) 
    273      
    27432class Accounter(AccounterBase) : 
    27533    def __init__(self, kotabackend, arguments) : 
     
    354112        cmdlower = commandline.lower() 
    355113        if cmdlower == "snmp" : 
    356             if hasSNMP : 
    357                 return self.askWithSNMP(printer) 
    358             else :     
    359                 raise PyKotaAccounterError, _("Internal SNMP accounting asked, but Python-SNMP is not available. Please download it from http://pysnmp.sourceforge.net") 
     114            return snmp.Handler(self, printer).retrieveInternalPageCounter() 
    360115        elif cmdlower == "pjl" : 
    361             return self.askWithPJL(printer) 
     116            return pjl.Handler(self, printer).retrieveInternalPageCounter() 
    362117             
    363118        if printer is None : 
     
    405160            else :         
    406161                return pagecounter         
    407          
    408     def askWithSNMP(self, printer) : 
    409         """Returns the page counter from the printer via internal SNMP handling.""" 
    410         acc = SNMPAccounter(self, printer) 
    411         try : 
    412             if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
    413                (os.environ.get("PYKOTAACTION") != "DENY") and \ 
    414                (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
    415                self.filter.jobSizeBytes : 
    416                 acc.waitPrinting() 
    417             acc.waitIdle()     
    418         except :     
    419             if acc.printerInternalPageCounter is None : 
    420                 raise 
    421             else :     
    422                 self.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (acc.printerInternalPageCounter, self.filter.printername), "warn") 
    423         return acc.printerInternalPageCounter 
    424          
    425     def askWithPJL(self, printer) : 
    426         """Returns the page counter from the printer via internal PJL handling.""" 
    427         acc = PJLAccounter(self, printer) 
    428         try : 
    429             if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
    430                (os.environ.get("PYKOTAACTION") != "DENY") and \ 
    431                (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
    432                self.filter.jobSizeBytes : 
    433                 acc.waitPrinting() 
    434             acc.waitIdle()     
    435         except :     
    436             if acc.printerInternalPageCounter is None : 
    437                 raise 
    438             else :     
    439                 self.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (acc.printerInternalPageCounter, self.filter.printername), "warn") 
    440         return acc.printerInternalPageCounter