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 | | |