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

Revision 2277, 8.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 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
25import sys
26import os
27import socket
28import time
29import signal
30
31# NB : in fact these variables don't do much, since the time
32# is in fact wasted in the sock.recv() blocking call, with the timeout
33ITERATIONDELAY = 1   # 1 Second
34STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
35
36# Here's the real thing :
37TIMEOUT = 3
38
39# Old method : pjlMessage = "\033%-12345X@PJL USTATUSOFF\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n\033%-12345X"
40# Here's a new method, which seems to work fine on my HP2300N, while the
41# previous one didn't.
42# TODO : We could also experiment with USTATUS JOB=ON and we would know for sure
43# when the job is finished, without having to poll the printer repeatedly.
44pjlMessage = "\033%-12345X@PJL USTATUS DEVICE=ON\r\n@PJL INFO STATUS\r\n@PJL INFO PAGECOUNT\r\n@PJL USTATUS DEVICE=OFF\033%-12345X"
45pjlStatusValues = {
46                    "10000" : "Powersave Mode",
47                    "10001" : "Ready Online",
48                    "10002" : "Ready Offline",
49                    "10003" : "Warming Up",
50                    "10004" : "Self Test",
51                    "10005" : "Reset",
52                    "10023" : "Printing",
53                    "35078" : "Powersave Mode",         # 10000 is ALSO powersave !!!
54                    "40000" : "Sleep Mode",             # Standby
55                  }
56                 
57class Handler :
58    """A class for PJL print accounting."""
59    def __init__(self, parent, printerhostname) :
60        self.parent = parent
61        self.printerHostname = printerhostname
62        self.printerInternalPageCounter = self.printerStatus = None
63        self.timedout = 0
64       
65    def alarmHandler(self, signum, frame) :   
66        """Query has timedout, handle this."""
67        self.timedout = 1
68        raise IOError, "Waiting for PJL answer timed out. Please try again later."
69       
70    def retrievePJLValues(self) :   
71        """Retrieves a printer's internal page counter and status via PJL."""
72        port = 9100
73        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
74        try :
75            sock.connect((self.printerHostname, port))
76        except socket.error, msg :
77            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
78        else :
79            try :
80                sock.send(pjlMessage)
81            except socket.error, msg :
82                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
83            else :   
84                actualpagecount = self.printerStatus = None
85                self.timedout = 0
86                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
87                    signal.signal(signal.SIGALRM, self.alarmHandler)
88                    signal.alarm(TIMEOUT)
89                    try :
90                        answer = sock.recv(1024)
91                    except IOError, msg :   
92                        break   # our alarm handler was launched, probably
93                    else :   
94                        readnext = 0
95                        self.parent.filter.logdebug("PJL answer : %s" % repr(answer))
96                        for line in [l.strip() for l in answer.split()] : 
97                            if line.startswith("CODE=") :
98                                self.printerStatus = line.split("=")[1]
99                            elif line.startswith("PAGECOUNT") :   
100                                readnext = 1 # page counter is on next line
101                            elif readnext :   
102                                actualpagecount = int(line.strip())
103                                readnext = 0
104                    signal.alarm(0)
105                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
106        sock.close()
107       
108    def waitPrinting(self) :
109        """Waits for printer status being 'printing'."""
110        firstvalue = None
111        while 1:
112            self.retrievePJLValues()
113            if self.printerStatus in ('10023', '10003') :
114                break
115            if self.printerInternalPageCounter is not None :   
116                if firstvalue is None :
117                    # first time we retrieved a page counter, save it
118                    firstvalue = self.printerInternalPageCounter
119                else :     
120                    # second time (or later)
121                    if firstvalue < self.printerInternalPageCounter :
122                        # Here we have a printer which lies :
123                        # it says it is not printing or warming up
124                        # BUT the page counter increases !!!
125                        # So we can probably quit being sure it is printing.
126                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
127                        break
128            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)
129            time.sleep(ITERATIONDELAY)
130       
131    def waitIdle(self) :
132        """Waits for printer status being 'idle'."""
133        idle_num = idle_flag = 0
134        while 1 :
135            self.retrievePJLValues()
136            idle_flag = 0
137            if self.printerStatus in ('10000', '10001', '35078', '40000') :
138                idle_flag = 1
139            if idle_flag :   
140                idle_num += 1
141                if idle_num >= STABILIZATIONDELAY :
142                    # printer status is stable, we can exit
143                    break
144            else :   
145                idle_num = 0
146            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)
147            time.sleep(ITERATIONDELAY)
148   
149    def retrieveInternalPageCounter(self) :
150        """Returns the page counter from the printer via internal PJL handling."""
151        try :
152            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
153               (os.environ.get("PYKOTAACTION") != "DENY") and \
154               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
155               self.parent.filter.jobSizeBytes :
156                self.waitPrinting()
157            self.waitIdle()   
158        except :   
159            if self.printerInternalPageCounter is None :
160                raise
161            else :   
162                self.parent.filter.printInfo(_("PJL querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.printername), "warn")
163        return self.printerInternalPageCounter
164           
165if __name__ == "__main__" :           
166    if len(sys.argv) != 2 :   
167        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
168    else :   
169        def _(msg) :
170            return msg
171           
172        class fakeFilter :
173            def __init__(self) :
174                self.printername = "FakePrintQueue"
175                self.jobSizeBytes = 1
176               
177            def printInfo(self, msg, level="info") :
178                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
179                sys.stderr.flush()
180               
181            def logdebug(self, msg) :   
182                self.printInfo(msg, "debug")
183               
184        class fakeAccounter :       
185            def __init__(self) :
186                self.filter = fakeFilter()
187                self.protocolHandler = Handler(self, sys.argv[1])
188           
189        acc = fakeAccounter()           
190        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.