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

Revision 2425, 10.1 kB (checked in by jerome, 19 years ago)

Allows 'hardware(pjl:port)' and 'hardware(snmp:community)'.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Rev Id
RevLine 
[2205]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
[2302]19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
[2205]20#
21# $Id$
22#
23#
24
25import sys
26import os
27import socket
28import time
29import signal
30
[2277]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
[2205]34STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
35
[2277]36# Here's the real thing :
[2365]37TIMEOUT = 5
[2277]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"
[2205]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
[2425]62        try :
63            self.port = int(self.parent.arguments.split(":")[1].strip())
64        except (IndexError, ValueError) :
65            self.port = 9100
[2205]66        self.printerInternalPageCounter = self.printerStatus = None
67        self.timedout = 0
68       
69    def alarmHandler(self, signum, frame) :   
70        """Query has timedout, handle this."""
71        self.timedout = 1
72        raise IOError, "Waiting for PJL answer timed out. Please try again later."
73       
74    def retrievePJLValues(self) :   
75        """Retrieves a printer's internal page counter and status via PJL."""
76        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
77        try :
[2423]78            sock.connect((self.printerHostname, self.port))
[2205]79        except socket.error, msg :
[2423]80            self.parent.filter.printInfo(_("Problem during connection to %s:%s : %s") % (self.printerHostname, self.port, msg), "warn")
[2205]81        else :
[2353]82            self.parent.filter.logdebug("Connected to printer %s" % self.printerHostname)
[2205]83            try :
84                sock.send(pjlMessage)
85            except socket.error, msg :
[2423]86                self.parent.filter.printInfo(_("Problem while sending PJL query to %s:%s : %s") % (self.printerHostname, self.port, msg), "warn")
[2205]87            else :   
[2353]88                self.parent.filter.logdebug("Query sent to %s : %s" % (self.printerHostname, repr(pjlMessage)))
[2205]89                actualpagecount = self.printerStatus = None
90                self.timedout = 0
91                while (self.timedout == 0) or (actualpagecount is None) or (self.printerStatus is None) :
92                    signal.signal(signal.SIGALRM, self.alarmHandler)
[2277]93                    signal.alarm(TIMEOUT)
[2205]94                    try :
95                        answer = sock.recv(1024)
96                    except IOError, msg :   
[2353]97                        self.parent.filter.logdebug("I/O Error [%s] : alarm handler probably called" % msg)
[2205]98                        break   # our alarm handler was launched, probably
[2378]99                    except socket.error :   
[2423]100                        self.parent.filter.printInfo(_("Problem while receiving PJL answer from %s:%s : %s") % (self.printerHostname, self.port, msg), "warn")
[2205]101                    else :   
102                        readnext = 0
[2277]103                        self.parent.filter.logdebug("PJL answer : %s" % repr(answer))
[2205]104                        for line in [l.strip() for l in answer.split()] : 
105                            if line.startswith("CODE=") :
106                                self.printerStatus = line.split("=")[1]
[2353]107                                self.parent.filter.logdebug("Found status : %s" % self.printerStatus)
[2378]108                            elif line.startswith("PAGECOUNT=") :   
109                                try :
110                                    actualpagecount = int(line.split('=')[1].strip())
111                                except ValueError :   
112                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
113                                else :
114                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
[2205]115                            elif line.startswith("PAGECOUNT") :   
116                                readnext = 1 # page counter is on next line
117                            elif readnext :   
[2377]118                                try :
119                                    actualpagecount = int(line.strip())
120                                except ValueError :   
[2378]121                                    self.parent.filter.logdebug("Received incorrect datas : [%s]" % line.strip())
[2377]122                                else :
123                                    self.parent.filter.logdebug("Found pages counter : %s" % actualpagecount)
124                                    readnext = 0
[2205]125                    signal.alarm(0)
126                self.printerInternalPageCounter = max(actualpagecount, self.printerInternalPageCounter)
127        sock.close()
[2353]128        self.parent.filter.logdebug("Connection to %s is now closed." % self.printerHostname)
[2205]129       
130    def waitPrinting(self) :
131        """Waits for printer status being 'printing'."""
132        firstvalue = None
133        while 1:
134            self.retrievePJLValues()
135            if self.printerStatus in ('10023', '10003') :
136                break
137            if self.printerInternalPageCounter is not None :   
138                if firstvalue is None :
139                    # first time we retrieved a page counter, save it
140                    firstvalue = self.printerInternalPageCounter
141                else :     
142                    # second time (or later)
143                    if firstvalue < self.printerInternalPageCounter :
144                        # Here we have a printer which lies :
145                        # it says it is not printing or warming up
146                        # BUT the page counter increases !!!
147                        # So we can probably quit being sure it is printing.
[2409]148                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn")
[2205]149                        break
[2409]150            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName)
[2205]151            time.sleep(ITERATIONDELAY)
152       
153    def waitIdle(self) :
154        """Waits for printer status being 'idle'."""
155        idle_num = idle_flag = 0
156        while 1 :
157            self.retrievePJLValues()
158            idle_flag = 0
159            if self.printerStatus in ('10000', '10001', '35078', '40000') :
160                idle_flag = 1
161            if idle_flag :   
162                idle_num += 1
[2277]163                if idle_num >= STABILIZATIONDELAY :
[2205]164                    # printer status is stable, we can exit
165                    break
166            else :   
167                idle_num = 0
[2409]168            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName)
[2205]169            time.sleep(ITERATIONDELAY)
170   
171    def retrieveInternalPageCounter(self) :
172        """Returns the page counter from the printer via internal PJL handling."""
173        try :
174            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
175               (os.environ.get("PYKOTAACTION") != "DENY") and \
176               (os.environ.get("PYKOTAPHASE") == "AFTER") and \
[2409]177               self.parent.filter.JobSizeBytes :
[2205]178                self.waitPrinting()
179            self.waitIdle()   
180        except :   
181            if self.printerInternalPageCounter is None :
182                raise
183            else :   
[2409]184                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")
[2205]185        return self.printerInternalPageCounter
186           
187if __name__ == "__main__" :           
188    if len(sys.argv) != 2 :   
189        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
190    else :   
191        def _(msg) :
192            return msg
193           
194        class fakeFilter :
195            def __init__(self) :
[2409]196                self.PrinterName = "FakePrintQueue"
197                self.JobSizeBytes = 1
[2205]198               
199            def printInfo(self, msg, level="info") :
200                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
201                sys.stderr.flush()
202               
203            def logdebug(self, msg) :   
204                self.printInfo(msg, "debug")
205               
206        class fakeAccounter :       
207            def __init__(self) :
208                self.filter = fakeFilter()
209                self.protocolHandler = Handler(self, sys.argv[1])
210           
211        acc = fakeAccounter()           
212        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.