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

Revision 2378, 10.0 kB (checked in by jerome, 19 years ago)

Another small fix, the Brother HL3400C doesn't answer the page counter
like other printers, giving an infinite loop.
Severity : Might be important, depending on the printers being used.

  • 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 = 5
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            self.parent.filter.logdebug("Connected to printer %s" % self.printerHostname)
80            try :
81                sock.send(pjlMessage)
82            except socket.error, msg :
83                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, port, msg), "warn")
84            else :   
85                self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(pjlMessage)))
86                actualpagecount = self.printerStatus = None
87                self.timedout = 0
88                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
89                    signal.signal(signal.SIGALRM, self.alarmHandler)
90                    signal.alarm(TIMEOUT)
91                    try :
92                        answer = sock.recv(1024)
93                    except IOError, msg :   
94                        self.parent.filter.logdebug("I/O Error [%s] : alarm handler probably called" % msg)
95                        break   # our alarm handler was launched, probably
96                    except socket.error :   
97                        self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, port, msg), "warn")
98                    else :   
99                        readnext = 0
100                        self.parent.filter.logdebug("PJL answer : %s" % repr(answer))
101                        for line in [l.strip() for l in answer.split()] : 
102                            if line.startswith("CODE=") :
103                                self.printerStatus = line.split("=")[1]
104                                self.parent.filter.logdebug("Found status : %s" % self.printerStatus)
105                            elif line.startswith("PAGECOUNT=") :   
106                                try :
107                                    actualpagecount = int(line.split('=')[1].strip())
108                                except ValueError :   
109                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
110                                else :
111                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
112                            elif line.startswith("PAGECOUNT") :   
113                                readnext = 1 # page counter is on next line
114                            elif readnext :   
115                                try :
116                                    actualpagecount = int(line.strip())
117                                except ValueError :   
118                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
119                                else :
120                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
121                                    readnext = 0
122                    signal.alarm(0)
123                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
124        sock.close()
125        self.parent.filter.logdebug("Connection to %s is now closed." % self.printerHostname)
126       
127    def waitPrinting(self) :
128        """Waits for printer status being 'printing'."""
129        firstvalue = None
130        while 1:
131            self.retrievePJLValues()
132            if self.printerStatus in ('10023', '10003') :
133                break
134            if self.printerInternalPageCounter is not None :   
135                if firstvalue is None :
136                    # first time we retrieved a page counter, save it
137                    firstvalue = self.printerInternalPageCounter
138                else :     
139                    # second time (or later)
140                    if firstvalue < self.printerInternalPageCounter :
141                        # Here we have a printer which lies :
142                        # it says it is not printing or warming up
143                        # BUT the page counter increases !!!
144                        # So we can probably quit being sure it is printing.
145                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
146                        break
147            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)
148            time.sleep(ITERATIONDELAY)
149       
150    def waitIdle(self) :
151        """Waits for printer status being 'idle'."""
152        idle_num = idle_flag = 0
153        while 1 :
154            self.retrievePJLValues()
155            idle_flag = 0
156            if self.printerStatus in ('10000', '10001', '35078', '40000') :
157                idle_flag = 1
158            if idle_flag :   
159                idle_num += 1
160                if idle_num >= STABILIZATIONDELAY :
161                    # printer status is stable, we can exit
162                    break
163            else :   
164                idle_num = 0
165            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)
166            time.sleep(ITERATIONDELAY)
167   
168    def retrieveInternalPageCounter(self) :
169        """Returns the page counter from the printer via internal PJL handling."""
170        try :
171            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
172               (os.environ.get("PYKOTAACTION") != "DENY") and \
173               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
174               self.parent.filter.jobSizeBytes :
175                self.waitPrinting()
176            self.waitIdle()   
177        except :   
178            if self.printerInternalPageCounter is None :
179                raise
180            else :   
181                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")
182        return self.printerInternalPageCounter
183           
184if __name__ == "__main__" :           
185    if len(sys.argv) != 2 :   
186        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
187    else :   
188        def _(msg) :
189            return msg
190           
191        class fakeFilter :
192            def __init__(self) :
193                self.printername = "FakePrintQueue"
194                self.jobSizeBytes = 1
195               
196            def printInfo(self, msg, level="info") :
197                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
198                sys.stderr.flush()
199               
200            def logdebug(self, msg) :   
201                self.printInfo(msg, "debug")
202               
203        class fakeAccounter :       
204            def __init__(self) :
205                self.filter = fakeFilter()
206                self.protocolHandler = Handler(self, sys.argv[1])
207           
208        acc = fakeAccounter()           
209        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.