Changeset 3025

Show
Ignore:
Timestamp:
10/04/06 00:21:34 (18 years ago)
Author:
jerome
Message:

Added the 'noprintingmaxdelay' directive to workaround printers
which don't conform to RFC3805.
Improved the reliability of SNMP and PJL hardware accounting.
PJL still needs some work though...

Location:
pykota/trunk
Files:
6 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/conf/pykota.conf.sample

    r3012 r3025  
    11431143 
    11441144 
     1145# Sets the maximum number of seconds to wait for the printer 
     1146# being in 'printing' mode once the job has been sent to it. 
     1147# Once this delay is expired, PyKota will consider this job 
     1148# will never be printed, aborts the hardware accounting 
     1149# process, and uses the latest internal page counter value seen. 
     1150#  
     1151# Increasing this value, or setting it to 0, may help with some  
     1152# printers which don't conform to RFC3805. Problem reported on a  
     1153# Samsung ML2551n gave a way for clever students to bypass 
     1154# hardware accounting entirely by removing the paper from 
     1155# the paper tray before the job had begun to print, then 
     1156# waiting 60 seconds, and putting the paper back in the tray... 
     1157# 
     1158# IMPORTANT : always ensure that your printers' firmware is up 
     1159# to date. 
     1160# 
     1161# This directive can be set either globally or on a per printer 
     1162# basis.  
     1163# 
     1164# When not set, an hardcoded value of 60 seconds is used. 
     1165# When set to 0, PyKota will wait indefinitely until the 
     1166# printer switches to the 'printing' status. 
     1167noprintingmaxdelay : 60 
  • pykota/trunk/NEWS

    r3018 r3025  
    2222PyKota NEWS : 
    2323        
     24    - 1.25alpha13 (2006-10-04) : 
     25      
     26        - Introduced the 'noprintingmaxdelay' directive to workaround 
     27          some buggy printers when hardware accounting is used. 
     28           
     29        - Improved the reliability of hardware accounting.   
     30           
    2431    - 1.25alpha12 (2006-09-15) : 
    2532     
  • pykota/trunk/pykota/accounters/pjl.py

    r2830 r3025  
    131131    def waitPrinting(self) : 
    132132        """Waits for printer status being 'printing'.""" 
     133        try : 
     134            noprintingmaxdelay = int(self.parent.filter.config.getNoPrintingMaxDelay(self.parent.filter.PrinterName)) 
     135        except (TypeError, AttributeError) : # NB : AttributeError in testing mode because I'm lazy ! 
     136            noprintingmaxdelay = NOPRINTINGMAXDELAY 
     137            self.parent.filter.logdebug("No max delay defined for printer %s, using %i seconds." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
     138        if not noprintingmaxdelay : 
     139            self.parent.filter.logdebug("Will wait indefinitely until printer %s is in 'printing' state." % self.parent.filter.PrinterName) 
     140        else :     
     141            self.parent.filter.logdebug("Will wait until printer %s is in 'printing' state or %i seconds have elapsed." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
    133142        previousValue = self.parent.getLastPageCounter() 
    134143        timebefore = time.time() 
     
    151160                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") 
    152161                        break 
    153                     elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : 
     162                    elif noprintingmaxdelay and ((time.time() - timebefore) > noprintingmaxdelay) : 
    154163                        # More than X seconds without the printer being in 'printing' mode 
    155164                        # We can safely assume this won't change if printer is now 'idle' 
  • pykota/trunk/pykota/accounters/snmp.py

    r2877 r3025  
    2222# 
    2323# 
     24 
     25"""This module is used to extract printer's internal page counter 
     26and status informations using SNMP queries. 
     27 
     28The values extracted are defined at least in RFC3805 and RFC2970. 
     29""" 
    2430 
    2531ITERATIONDELAY = 1.5   # 1.5 Second 
     
    6571                     }   
    6672hrPrinterDetectedErrorStateOID = "1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 
     73printerDetectedErrorStateValues = [ { 128 : 'Low Paper', 
     74                                       64 : 'No Paper', 
     75                                       32 : 'Low Toner', 
     76                                       16 : 'No Toner', 
     77                                        8 : 'Door Open', 
     78                                        4 : 'Jammed', 
     79                                        2 : 'Offline', 
     80                                        1 : 'Service Requested', 
     81                                    }, 
     82                                    { 128 : 'Input Tray Missing', 
     83                                       64 : 'Output Tray Missing', 
     84                                       32 : 'Marker Supply Missing', 
     85                                       16 : 'Output Near Full', 
     86                                        8 : 'Output Full', 
     87                                        4 : 'Input Tray Empty', 
     88                                        2 : 'Overdue Preventive Maintainance', 
     89                                        1 : 'Not Assigned in RFC3805', 
     90                                    }, 
     91                                  ]   
     92errorConditions = [ 'No Paper', 
     93                    # 'No Toner', 
     94                    'Door Open', 
     95                    'Jammed', 
     96                    'Offline', 
     97                    'Service Requested', 
     98                    'Input Tray Missing', 
     99                    'Output Tray Missing', 
     100                    # 'Marker Supply Missing', 
     101                    'Output Full', 
     102                    'Input Tray Empty', 
     103                  ] 
    67104prtConsoleDisplayBufferTextOID = "1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1 
     105 
    68106class BaseHandler : 
    69107    """A class for SNMP print accounting.""" 
     
    76114            self.community = "public" 
    77115        self.port = 161 
     116        self.initValues() 
     117         
     118    def initValues(self) :     
     119        """Initializes SNMP values.""" 
    78120        self.printerInternalPageCounter = None 
    79121        self.printerStatus = None 
    80122        self.deviceStatus = None 
     123        self.printerDetectedErrorState = None 
     124        self.consoleDisplayBufferText = None 
     125        self.timebefore = time.time()   # resets timer also in case of error 
    81126         
    82127    def retrieveSNMPValues(self) :     
     
    84129        raise RuntimeError, "You have to overload this method." 
    85130         
     131    def extractErrorStates(self, value) :     
     132        """Returns a list of textual error states from a binary value.""" 
     133        states = [] 
     134        for i in range(min(len(value), len(printerDetectedErrorStateValues))) : 
     135            byte = ord(value[i]) 
     136            bytedescription = printerDetectedErrorStateValues[i] 
     137            for (k, v) in bytedescription.items() : 
     138                if byte & k : 
     139                    states.append(v) 
     140        return states             
     141         
     142    def checkIfError(self, errorstates) :     
     143        """Checks if any error state is fatal or not.""" 
     144        for err in errorstates : 
     145            if err in errorConditions : 
     146                return True 
     147        return False     
     148         
    86149    def waitPrinting(self) : 
    87150        """Waits for printer status being 'printing'.""" 
     151        try : 
     152            noprintingmaxdelay = int(self.parent.filter.config.getNoPrintingMaxDelay(self.parent.filter.PrinterName)) 
     153        except (TypeError, AttributeError) : # NB : AttributeError in testing mode because I'm lazy ! 
     154            noprintingmaxdelay = NOPRINTINGMAXDELAY 
     155            self.parent.filter.logdebug("No max delay defined for printer %s, using %i seconds." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
     156        if not noprintingmaxdelay : 
     157            self.parent.filter.logdebug("Will wait indefinitely until printer %s is in 'printing' state." % self.parent.filter.PrinterName) 
     158        else :     
     159            self.parent.filter.logdebug("Will wait until printer %s is in 'printing' state or %i seconds have elapsed." % (self.parent.filter.PrinterName, noprintingmaxdelay)) 
    88160        previousValue = self.parent.getLastPageCounter() 
    89         timebefore = time.time() 
    90161        firstvalue = None 
    91162        while 1: 
     
    107178                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") 
    108179                        break 
    109                     elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : 
     180                    elif noprintingmaxdelay \ 
     181                         and ((time.time() - self.timebefore) > noprintingmaxdelay) \ 
     182                         and not self.checkIfError(self.printerDetectedErrorState) : 
    110183                        # More than X seconds without the printer being in 'printing' mode 
    111184                        # We can safely assume this won't change if printer is now 'idle' 
     
    135208            dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
    136209            idle_flag = 0 
    137             if (pstatusAsString == 'idle') or \ 
    138                ((pstatusAsString == 'other') and \ 
    139                 (dstatusAsString == 'running')) : 
     210            if (not self.checkIfError(self.printerDetectedErrorState)) \ 
     211               and ((pstatusAsString == 'idle') or \ 
     212                         ((pstatusAsString == 'other') and \ 
     213                          (dstatusAsString == 'running'))) : 
    140214                idle_flag = 1       # Standby / Powersave is considered idle 
    141215            if idle_flag :     
     
    173247                                                  tuple([int(i) for i in pageCounterOID.split('.')]), \ 
    174248                                                  tuple([int(i) for i in hrPrinterStatusOID.split('.')]), \ 
    175                                                   tuple([int(i) for i in hrDeviceStatusOID.split('.')])) 
     249                                                  tuple([int(i) for i in hrDeviceStatusOID.split('.')]), \ 
     250                                                  tuple([int(i) for i in hrPrinterDetectedErrorStateOID.split('.')]), \ 
     251                                                  tuple([int(i) for i in prtConsoleDisplayBufferTextOID.split('.')])) 
    176252            if errorIndication :                                                   
    177253                self.parent.filter.printInfo("SNMP Error : %s" % errorIndication, "error") 
     254                self.initValues() 
    178255            elif errorStatus :     
    179256                self.parent.filter.printInfo("SNMP Error : %s at %s" % (errorStatus.prettyPrint(), \ 
     
    184261                self.printerStatus = int(varBinds[1][1].prettyPrint()) 
    185262                self.deviceStatus = int(varBinds[2][1].prettyPrint()) 
    186                 self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s  PrinterStatus : '%s'  DeviceStatus : '%s'" \ 
     263                self.printerDetectedErrorState = self.extractErrorStates(str(varBinds[3][1])) 
     264                self.consoleDisplayBufferText = varBinds[4][1].prettyPrint() 
     265                self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s  PrinterStatus : '%s'  DeviceStatus : '%s'  PrinterErrorState : '%s'  ConsoleDisplayBuffer : '%s'" \ 
    187266                     % (self.printerInternalPageCounter, \ 
    188267                        printerStatusValues.get(self.printerStatus), \ 
    189                         deviceStatusValues.get(self.deviceStatus))) 
     268                        deviceStatusValues.get(self.deviceStatus), \ 
     269                        self.printerDetectedErrorState, \ 
     270                        self.consoleDisplayBufferText)) 
    190271else : 
    191272    class Handler(BaseHandler) : 
     
    199280            req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \ 
    200281                                                        (hrPrinterStatusOID, ver.Null()), \ 
    201                                                         (hrDeviceStatusOID, ver.Null())) 
     282                                                        (hrDeviceStatusOID, ver.Null()), \ 
     283                                                        (hrPrinterDetectedErrorStateOID, ver.Null()), \ 
     284                                                        (prtConsoleDisplayBufferTextOID, ver.Null())) 
    202285            tsp = Manager() 
    203286            try : 
     
    211294        def handleAnswer(self, wholeMsg, notusedhere, req): 
    212295            """Decodes and handles the SNMP answer.""" 
    213             self.parent.filter.logdebug("SNMP answer : '%s'" % repr(wholeMsg)) 
    214296            ver = alpha.protoVersions[alpha.protoVersionId1] 
    215297            rsp = ver.Message() 
     
    232314                            self.printerStatus = self.values[1] 
    233315                            self.deviceStatus = self.values[2] 
    234                             self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s  PrinterStatus : '%s'  DeviceStatus : '%s'" \ 
     316                            self.printerDetectedErrorState = self.extractErrorStates(self.values[3]) 
     317                            self.consoleDisplayBufferText = self.values[4] 
     318                            self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s  PrinterStatus : '%s'  DeviceStatus : '%s'  PrinterErrorState : '%s'  ConsoleDisplayBuffer : '%s'" \ 
    235319                                 % (self.printerInternalPageCounter, \ 
    236320                                    printerStatusValues.get(self.printerStatus), \ 
    237                                     deviceStatusValues.get(self.deviceStatus))) 
     321                                    deviceStatusValues.get(self.deviceStatus), \ 
     322                                    self.printerDetectedErrorState, \ 
     323                                    self.consoleDisplayBufferText)) 
    238324                        except IndexError :     
    239325                            self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values)) 
  • pykota/trunk/pykota/config.py

    r2909 r3025  
    557557                raise PyKotaConfigError, _("Incorrect value %s for the duplicatesdelay directive in section %s") % (str(duplicatesdelay), printername) 
    558558         
     559    def getNoPrintingMaxDelay(self, printername) :           
     560        """Returns the max number of seconds to wait for the printer to be in 'printing' mode.""" 
     561        try :  
     562            maxdelay = self.getPrinterOption(printername, "noprintingmaxdelay") 
     563        except PyKotaConfigError :     
     564            return None         # tells to use hardcoded value 
     565        else :     
     566            try : 
     567                maxdelay = int(maxdelay) 
     568                if maxdelay < 0 : 
     569                    raise ValueError 
     570            except (TypeError, ValueError) : 
     571                raise PyKotaConfigError, _("Incorrect value %s for the noprintingmaxdelay directive in section %s") % (str(maxdelay), printername) 
     572            else :     
     573                return maxdelay 
     574         
    559575    def getWinbindSeparator(self) :           
    560576        """Returns the winbind separator's value if it is set, else None.""" 
  • pykota/trunk/pykota/version.py

    r3018 r3025  
    2222# 
    2323 
    24 __version__ = "1.25alpha12_unofficial" 
     24__version__ = "1.25alpha13_unofficial" 
    2525 
    2626__doc__ = "PyKota : a complete Printing Quota Solution for CUPS."