1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# (c) Copyright 2003-2015 HP Development Company, L.P.
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19#
20# Author: Don Welch, Narla Naga Samrat Chowdary, Yashwant Kumar Sahu
21#
22
23
24
25# Std Lib
26import struct
27import io
28from .sixext import BytesIO, to_bytes_utf8, to_bytes_latin, to_string_latin, to_long
29
30from .g import *
31import xml.parsers.expat as expat
32import re
33import cupsext
34
35try:
36    from xml.etree import ElementTree
37    etree_loaded = True
38except ImportError:
39    try:
40        from elementtree.ElementTree import XML
41        elementtree_loaded = True
42    except ImportError:
43        elementtree_loaded = False
44    etree_loaded = False
45
46# Local
47from .g import *
48from .codes import *
49from . import pml, utils
50import hpmudext
51"""
52status dict structure:
53    { 'revision' :     STATUS_REV_00 .. STATUS_REV_04,
54      'agents' :       [ list of pens/agents/supplies (dicts) ],
55      'top-door' :     TOP_DOOR_NOT_PRESENT | TOP_DOOR_CLOSED | TOP_DOOR_OPEN,
56      'status-code' :  STATUS_...,
57      'supply-door' :  SUPPLY_DOOR_NOT_PRESENT | SUPPLY_DOOR_CLOSED | SUPPLY_DOOR_OPEN.
58      'duplexer' :     DUPLEXER_NOT_PRESENT | DUPLEXER_DOOR_CLOSED | DUPLEXER_DOOR_OPEN,
59      'photo_tray' :   PHOTO_TRAY_NOT_PRESENT | PHOTO_TRAY_ENGAGED | PHOTO_TRAY_NOT_ENGAGED,
60      'in-tray1' :     IN_TRAY_NOT_PRESENT | IN_TRAY_CLOSED | IN_TRAY_OPEN (| IN_TRAY_DEFAULT | IN_TRAY_LOCKED)*,
61      'in-tray2' :     IN_TRAY_NOT_PRESENT | IN_TRAY_CLOSED | IN_TRAY_OPEN (| IN_TRAY_DEFAULT | IN_TRAY_LOCKED)*,
62      'media-path' :   MEDIA_PATH_NOT_PRESENT | MEDIA_PATH_CUT_SHEET | MEDIA_PATH_BANNER | MEDIA_PATH_PHOTO,
63    }
64
65    * S:02 only
66
67agent dict structure: (pens/supplies/agents/etc)
68    { 'kind' :           AGENT_KIND_NONE ... AGENT_KIND_ADF_KIT,
69      'type' :           TYPE_BLACK ... AGENT_TYPE_UNSPECIFIED,      # aka color
70      'health' :         AGENT_HEALTH_OK ... AGENT_HEALTH_UNKNOWN,
71      'level' :          0 ... 100,
72      'level-trigger' :  AGENT_LEVEL_TRIGGER_SUFFICIENT_0 ... AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
73    }
74"""
75
76
77
78# 'revision'
79STATUS_REV_00 = 0x00
80STATUS_REV_01 = 0x01
81STATUS_REV_02 = 0x02
82STATUS_REV_03 = 0x03
83STATUS_REV_04 = 0x04
84STATUS_REV_V  = 0xff
85STATUS_REV_UNKNOWN = 0xfe
86
87vstatus_xlate  = {'busy' : STATUS_PRINTER_BUSY,
88                   'idle' : STATUS_PRINTER_IDLE,
89                   'prnt' : STATUS_PRINTER_PRINTING,
90                   'offf' : STATUS_PRINTER_TURNING_OFF,
91                   'rprt' : STATUS_PRINTER_REPORT_PRINTING,
92                   'cncl' : STATUS_PRINTER_CANCELING,
93                   'iost' : STATUS_PRINTER_IO_STALL,
94                   'dryw' : STATUS_PRINTER_DRY_WAIT_TIME,
95                   'penc' : STATUS_PRINTER_PEN_CHANGE,
96                   'oopa' : STATUS_PRINTER_OUT_OF_PAPER,
97                   'bnej' : STATUS_PRINTER_BANNER_EJECT,
98                   'bnmz' : STATUS_PRINTER_BANNER_MISMATCH,
99                   'phmz' : STATUS_PRINTER_PHOTO_MISMATCH,
100                   'dpmz' : STATUS_PRINTER_DUPLEX_MISMATCH,
101                   'pajm' : STATUS_PRINTER_MEDIA_JAM,
102                   'cars' : STATUS_PRINTER_CARRIAGE_STALL,
103                   'paps' : STATUS_PRINTER_PAPER_STALL,
104                   'penf' : STATUS_PRINTER_PEN_FAILURE,
105                   'erro' : STATUS_PRINTER_HARD_ERROR,
106                   'pwdn' : STATUS_PRINTER_POWER_DOWN,
107                   'fpts' : STATUS_PRINTER_FRONT_PANEL_TEST,
108                   'clno' : STATUS_PRINTER_CLEAN_OUT_TRAY_MISSING}
109
110REVISION_2_TYPE_MAP = {0 : AGENT_TYPE_NONE,
111                        1 : AGENT_TYPE_BLACK,
112                        2 : AGENT_TYPE_CYAN,
113                        3 : AGENT_TYPE_MAGENTA,
114                        4 : AGENT_TYPE_YELLOW,
115                        5 : AGENT_TYPE_BLACK,
116                        6 : AGENT_TYPE_CYAN,
117                        7 : AGENT_TYPE_MAGENTA,
118                        8 : AGENT_TYPE_YELLOW,
119                       }
120
121STATUS_BLOCK_UNKNOWN = {'revision' : STATUS_REV_UNKNOWN,
122                         'agents' : [],
123                         'status-code' : STATUS_UNKNOWN,
124                       }
125
126NUM_PEN_POS = {STATUS_REV_00 : 16,
127                STATUS_REV_01 : 16,
128                STATUS_REV_02 : 16,
129                STATUS_REV_03 : 18,
130                STATUS_REV_04 : 22}
131
132PEN_DATA_SIZE = {STATUS_REV_00 : 8,
133                  STATUS_REV_01 : 8,
134                  STATUS_REV_02 : 4,
135                  STATUS_REV_03 : 8,
136                  STATUS_REV_04 : 8}
137
138STATUS_POS = {STATUS_REV_00 : 14,
139               STATUS_REV_01 : 14,
140               STATUS_REV_02 : 14,
141               STATUS_REV_03 : 16,
142               STATUS_REV_04 : 20}
143
144def parseSStatus(s, z=''):
145    revision = ''
146    pens = []
147    top_door = TOP_DOOR_NOT_PRESENT
148    stat = STATUS_UNKNOWN
149    supply_door = SUPPLY_DOOR_NOT_PRESENT
150    duplexer = DUPLEXER_NOT_PRESENT
151    photo_tray = PHOTO_TRAY_NOT_PRESENT
152    in_tray1 = IN_TRAY_NOT_PRESENT
153    in_tray2 = IN_TRAY_NOT_PRESENT
154    media_path = MEDIA_PATH_NOT_PRESENT
155    Z_SIZE = 6
156
157    try:
158        z1 = []
159        if len(z) > 0:
160            z_fields = z.split(',')
161
162            for z_field in z_fields:
163
164                if len(z_field) > 2 and z_field[:2] == '05':
165                    z1s = z_field[2:]
166                    z1 = [int(x, 16) for x in z1s]
167
168        s1 = [int(x, 16) for x in s]
169
170        revision = s1[1]
171
172        assert STATUS_REV_00 <= revision <= STATUS_REV_04
173
174        top_door = bool(s1[2] & to_long(0x8)) + s1[2] & to_long(0x1)
175        supply_door = bool(s1[3] & to_long(0x8)) + s1[3] & to_long(0x1)
176        duplexer = bool(s1[4] & to_long(0xc)) +  s1[4] & to_long(0x1)
177        photo_tray = bool(s1[5] & 0x8) + s1[5] & 0x1
178
179        if revision == STATUS_REV_02:
180            in_tray1 = bool(s1[6] & to_long(0x8)) + s1[6] & to_long(0x1)
181            in_tray2 = bool(s1[7] & to_long(0x8)) + s1[7] & to_long(0x1)
182        else:
183            in_tray1 = bool(s1[6] & to_long(0x8))
184            in_tray2 = bool(s1[7] & to_long(0x8))
185
186        media_path = bool(s1[8] & to_long(0x8)) + (s1[8] & to_long(0x1)) + ((bool(s1[18] & to_long(0x2)))<<1)
187        status_pos = STATUS_POS[revision]
188        status_byte = s1[status_pos]<<4
189        if status_byte != 48:
190            status_byte = (s1[status_pos]<<4) + s1[status_pos + 1]
191        stat = status_byte + STATUS_PRINTER_BASE
192
193        pen, c, d = {}, NUM_PEN_POS[revision]+1, 0
194        num_pens = s1[NUM_PEN_POS[revision]]
195        index = 0
196        pen_data_size = PEN_DATA_SIZE[revision]
197
198        log.debug("num_pens = %d" % num_pens)
199        for p in range(num_pens):
200            info = int(s[c : c + pen_data_size], 16)
201
202            pen['index'] = index
203
204            if pen_data_size == 4:
205                pen['type'] = REVISION_2_TYPE_MAP.get(int((info & to_long(0xf000)) >> to_long(12)), 0)
206
207                if index < (num_pens / 2):
208                    pen['kind'] = AGENT_KIND_HEAD
209                else:
210                    pen['kind'] = AGENT_KIND_SUPPLY
211
212                pen['level-trigger'] = int ((info & to_long(0x0e00)) >> to_long(9))
213                pen['health'] = int((info & to_long(0x0180)) >> to_long(7))
214                pen['level'] = int(info & to_long(0x007f))
215                pen['id'] = 0x1f
216
217            elif pen_data_size == 8:
218                pen['kind'] = bool(info & to_long(0x80000000)) + ((bool(info & to_long(0x40000000)))<<to_long(1))
219                pen['type'] = int((info & to_long(0x3f000000)) >> to_long(24))
220                pen['id'] = int((info & 0xf80000) >> to_long(19))
221                pen['level-trigger'] = int((info & to_long(0x70000)) >> to_long(16))
222                pen['health'] = int((info & to_long(0xc000)) >> to_long(14))
223                pen['level'] = int(info & to_long(0xff))
224
225            else:
226                log.error("Pen data size error")
227
228            if len(z1) > 0:
229                # TODO: Determine cause of IndexError for C6100 (defect #1111)
230                try:
231                    pen['dvc'] = int(z1s[d+1:d+5], 16)
232                    pen['virgin'] = bool(z1[d+5] & to_long(0x8))
233                    pen['hp-ink'] = bool(z1[d+5] & to_long(0x4))
234                    pen['known'] = bool(z1[d+5] & to_long(0x2))
235                    pen['ack'] = bool(z1[d+5] & to_long(0x1))
236                except IndexError:
237                    pen['dvc'] = 0
238                    pen['virgin'] = 0
239                    pen['hp-ink'] = 0
240                    pen['known'] = 0
241                    pen['ack'] = 0
242
243            log.debug("pen %d %s" % (index, pen))
244
245            index += 1
246            pens.append(pen)
247            pen = {}
248            c += pen_data_size
249            d += Z_SIZE
250
251    except (IndexError, ValueError, TypeError) as e:
252        log.warn("Status parsing error: %s" % str(e))
253
254    return {'revision' :    revision,
255             'agents' :      pens,
256             'top-door' :    top_door,
257             'status-code' : stat,
258             'supply-door' : supply_door,
259             'duplexer' :    duplexer,
260             'photo-tray' :  photo_tray,
261             'in-tray1' :    in_tray1,
262             'in-tray2' :    in_tray2,
263             'media-path' :  media_path,
264           }
265
266
267
268# $HB0$NC0,ff,DN,IDLE,CUT,K0,C0,DP,NR,KP092,CP041
269#     0    1  2  3    4   5  6  7  8  9     10
270def parseVStatus(s):
271    pens, pen, c = [], {}, 0
272    fields = s.split(',')
273    log.debug(fields)
274    f0 = fields[0]
275
276    if len(f0) == 20:
277        # TODO: $H00000000$M00000000 style (OJ Pro 1150/70)
278        # Need spec
279        pass
280    elif len(f0) == 8:
281        for p in f0:
282            if c == 0:
283                #assert p == '$'
284                c += 1
285            elif c == 1:
286                if p in ('a', 'A'):
287                    pen['type'], pen['kind'] = AGENT_TYPE_NONE, AGENT_KIND_NONE
288                c += 1
289            elif c == 2:
290                pen['health'] = AGENT_HEALTH_OK
291                pen['kind'] = AGENT_KIND_HEAD_AND_SUPPLY
292                if   p in ('b', 'B'): pen['type'] = AGENT_TYPE_BLACK
293                elif p in ('c', 'C'): pen['type'] = AGENT_TYPE_CMY
294                elif p in ('d', 'D'): pen['type'] = AGENT_TYPE_KCM
295                elif p in ('u', 'U'): pen['type'], pen['health'] = AGENT_TYPE_NONE, AGENT_HEALTH_MISINSTALLED
296                c += 1
297            elif c == 3:
298                if p == '0': pen['state'] = 1
299                else: pen['state'] = 0
300
301                pen['level'] = 0
302                i = 8
303
304                while True:
305                    try:
306                        f = fields[i]
307                    except IndexError:
308                        break
309                    else:
310                        if f[:2] == 'KP' and pen['type'] == AGENT_TYPE_BLACK:
311                            pen['level'] = int(f[2:])
312                        elif f[:2] == 'CP' and pen['type'] == AGENT_TYPE_CMY:
313                            pen['level'] = int(f[2:])
314                    i += 1
315
316                pens.append(pen)
317                pen = {}
318                c = 0
319    else:
320        pass
321
322    try:
323        fields[2]
324    except IndexError:
325        top_lid = 1 # something went wrong!
326    else:
327        if fields[2] == 'DN':
328            top_lid = 1
329        else:
330            top_lid = 2
331
332    try:
333        stat = vstatus_xlate.get(fields[3].lower(), STATUS_PRINTER_IDLE)
334    except IndexError:
335        stat = STATUS_PRINTER_IDLE # something went wrong!
336
337    return {'revision' :   STATUS_REV_V,
338             'agents' :     pens,
339             'top-door' :   top_lid,
340             'status-code': stat,
341             'supply-door': SUPPLY_DOOR_NOT_PRESENT,
342             'duplexer' :   DUPLEXER_NOT_PRESENT,
343             'photo-tray' : PHOTO_TRAY_NOT_PRESENT,
344             'in-tray1' :   IN_TRAY_NOT_PRESENT,
345             'in-tray2' :   IN_TRAY_NOT_PRESENT,
346             'media-path' : MEDIA_PATH_CUT_SHEET, # ?
347           }
348
349
350def parseStatus(DeviceID):
351    if 'VSTATUS' in DeviceID:
352         return parseVStatus(DeviceID['VSTATUS'])
353    elif 'S' in DeviceID:
354        return parseSStatus(DeviceID['S'], DeviceID.get('Z', ''))
355    else:
356        return STATUS_BLOCK_UNKNOWN
357
358def LaserJetDeviceStatusToPrinterStatus(device_status, printer_status, detected_error_state):
359    stat = STATUS_PRINTER_IDLE
360
361    if device_status in (pml.DEVICE_STATUS_WARNING, pml.DEVICE_STATUS_DOWN):
362
363        if detected_error_state & pml.DETECTED_ERROR_STATE_LOW_PAPER_MASK and \
364            not (detected_error_state & pml.DETECTED_ERROR_STATE_NO_PAPER_MASK):
365            stat = STATUS_PRINTER_LOW_PAPER
366
367        elif detected_error_state & pml.DETECTED_ERROR_STATE_NO_PAPER_MASK:
368            stat = STATUS_PRINTER_OUT_OF_PAPER
369
370        elif detected_error_state & pml.DETECTED_ERROR_STATE_DOOR_OPEN_MASK:
371            stat = STATUS_PRINTER_DOOR_OPEN
372
373        elif detected_error_state & pml.DETECTED_ERROR_STATE_JAMMED_MASK:
374            stat = STATUS_PRINTER_MEDIA_JAM
375
376        elif detected_error_state & pml.DETECTED_ERROR_STATE_OUT_CART_MASK:
377            stat = STATUS_PRINTER_NO_TONER
378
379        elif detected_error_state & pml.DETECTED_ERROR_STATE_LOW_CART_MASK:
380            stat = STATUS_PRINTER_LOW_TONER
381
382        elif detected_error_state == pml.DETECTED_ERROR_STATE_SERVICE_REQUEST_MASK:
383            stat = STATUS_PRINTER_SERVICE_REQUEST
384
385        elif detected_error_state & pml.DETECTED_ERROR_STATE_OFFLINE_MASK:
386            stat = STATUS_PRINTER_OFFLINE
387
388    else:
389
390        if printer_status == pml.PRINTER_STATUS_IDLE:
391            stat = STATUS_PRINTER_IDLE
392
393        elif printer_status == pml.PRINTER_STATUS_PRINTING:
394            stat = STATUS_PRINTER_PRINTING
395
396        elif printer_status == pml.PRINTER_STATUS_WARMUP:
397            stat = STATUS_PRINTER_WARMING_UP
398
399    return stat
400
401# Map from ISO 10175/10180 to HPLIP types
402COLORANT_INDEX_TO_AGENT_TYPE_MAP = {
403                                    'other' :   AGENT_TYPE_UNSPECIFIED,
404                                    'unknown' : AGENT_TYPE_UNSPECIFIED,
405                                    'blue' :    AGENT_TYPE_BLUE,
406                                    'cyan' :    AGENT_TYPE_CYAN,
407                                    'magenta':  AGENT_TYPE_MAGENTA,
408                                    'yellow' :  AGENT_TYPE_YELLOW,
409                                    'black' :   AGENT_TYPE_BLACK,
410                                    'photoblack': AGENT_TYPE_PHOTO_BLACK,
411                                    'matteblack' : AGENT_TYPE_MATTE_BLACK,
412                                    'lightgray' : AGENT_TYPE_LG,
413                                    'gray': AGENT_TYPE_G,
414                                    'darkgray': AGENT_TYPE_DG,
415                                    'lightcyan': AGENT_TYPE_LC,
416                                    'lightmagenta': AGENT_TYPE_LM,
417                                    'red' : AGENT_TYPE_RED,
418                                   }
419
420MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP = {
421    pml.OID_MARKER_SUPPLIES_TYPE_OTHER :              AGENT_KIND_UNKNOWN,
422    pml.OID_MARKER_SUPPLIES_TYPE_UNKNOWN :            AGENT_KIND_UNKNOWN,
423    pml.OID_MARKER_SUPPLIES_TYPE_TONER :              AGENT_KIND_TONER_CARTRIDGE,
424    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_TONER :        AGENT_KIND_UNKNOWN,
425    pml.OID_MARKER_SUPPLIES_TYPE_INK :                AGENT_KIND_SUPPLY,
426    pml.OID_MARKER_SUPPLIES_TYPE_INK_CART :           AGENT_KIND_HEAD_AND_SUPPLY,
427    pml.OID_MARKER_SUPPLIES_TYPE_INK_RIBBON :         AGENT_KIND_HEAD_AND_SUPPLY,
428    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_INK :          AGENT_KIND_UNKNOWN,
429    pml.OID_MARKER_SUPPLIES_TYPE_OPC :                AGENT_KIND_DRUM_KIT,
430    pml.OID_MARKER_SUPPLIES_TYPE_DEVELOPER :          AGENT_KIND_UNKNOWN,
431    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OIL :          AGENT_KIND_UNKNOWN,
432    pml.OID_MARKER_SUPPLIES_TYPE_SOLID_WAX :          AGENT_KIND_UNKNOWN,
433    pml.OID_MARKER_SUPPLIES_TYPE_RIBBON_WAX :         AGENT_KIND_UNKNOWN,
434    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_WAX :          AGENT_KIND_UNKNOWN,
435    pml.OID_MARKER_SUPPLIES_TYPE_FUSER :              AGENT_KIND_MAINT_KIT,
436    pml.OID_MARKER_SUPPLIES_TYPE_CORONA_WIRE :        AGENT_KIND_UNKNOWN,
437    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OIL_WICK :     AGENT_KIND_UNKNOWN,
438    pml.OID_MARKER_SUPPLIES_TYPE_CLEANER_UNIT :       AGENT_KIND_UNKNOWN,
439    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_CLEANING_PAD : AGENT_KIND_UNKNOWN,
440    pml.OID_MARKER_SUPPLIES_TYPE_TRANSFER_UNIT :      AGENT_KIND_TRANSFER_KIT,
441    pml.OID_MARKER_SUPPLIES_TYPE_TONER_CART :         AGENT_KIND_TONER_CARTRIDGE,
442    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OILER :        AGENT_KIND_UNKNOWN,
443    pml.OID_MARKER_SUPPLIES_TYPE_ADF_MAINT_KIT :      AGENT_KIND_ADF_KIT,
444}
445
446
447def StatusType3( dev, parsedID ): # LaserJet Status (PML/SNMP)
448    try:
449        dev.openPML()
450        #result_code, on_off_line = dev.getPML( pml.OID_ON_OFF_LINE, pml.INT_SIZE_BYTE )
451        #result_code, sleep_mode = dev.getPML( pml.OID_SLEEP_MODE, pml.INT_SIZE_BYTE )
452        result_code, printer_status = dev.getPML( pml.OID_PRINTER_STATUS, pml.INT_SIZE_BYTE )
453        result_code, device_status = dev.getPML( pml.OID_DEVICE_STATUS, pml.INT_SIZE_BYTE )
454        result_code, cover_status = dev.getPML( pml.OID_COVER_STATUS, pml.INT_SIZE_BYTE )
455        result_code, value = dev.getPML( pml.OID_DETECTED_ERROR_STATE )
456    except Error:
457       dev.closePML()
458
459       return {'revision' :    STATUS_REV_UNKNOWN,
460                 'agents' :      [],
461                 'top-door' :    0,
462                 'status-code' : STATUS_UNKNOWN,
463                 'supply-door' : 0,
464                 'duplexer' :    1,
465                 'photo-tray' :  0,
466                 'in-tray1' :    0,
467                 'in-tray2' :    0,
468                 'media-path' :  0,
469               }
470
471    try:
472        detected_error_state = struct.unpack( 'B', to_bytes_latin(value[0]))[0]
473    except (IndexError, TypeError):
474        detected_error_state = pml.DETECTED_ERROR_STATE_OFFLINE_MASK
475
476    agents, x = [], 1
477
478    while True:
479        log.debug( "%s Agent: %d %s" % ("*"*10, x, "*"*10))
480        log.debug("OID_MARKER_SUPPLIES_TYPE_%d:" % x)
481        oid = ( pml.OID_MARKER_SUPPLIES_TYPE_x % x, pml.OID_MARKER_SUPPLIES_TYPE_x_TYPE )
482        result_code, value = dev.getPML( oid, pml.INT_SIZE_BYTE )
483
484        if result_code != ERROR_SUCCESS or value is None:
485            log.debug("End of supply information.")
486            break
487
488        for a in MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP:
489            if value == a:
490                agent_kind = MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP[a]
491                break
492        else:
493            agent_kind = AGENT_KIND_UNKNOWN
494
495        # TODO: Deal with printers that return -1 and -2 for level and max (LJ3380)
496
497        log.debug("OID_MARKER_SUPPLIES_LEVEL_%d:" % x)
498        oid = ( pml.OID_MARKER_SUPPLIES_LEVEL_x % x, pml.OID_MARKER_SUPPLIES_LEVEL_x_TYPE )
499        result_code, agent_level = dev.getPML( oid )
500
501        if result_code != ERROR_SUCCESS:
502            log.debug("Failed")
503            break
504
505        log.debug( 'agent%d-level: %d' % ( x, agent_level ) )
506        log.debug("OID_MARKER_SUPPLIES_MAX_%d:" % x)
507        oid = ( pml.OID_MARKER_SUPPLIES_MAX_x % x, pml.OID_MARKER_SUPPLIES_MAX_x_TYPE )
508        result_code, agent_max = dev.getPML( oid )
509
510        if agent_max == 0: agent_max = 1
511
512        if result_code != ERROR_SUCCESS:
513            log.debug("Failed")
514            break
515
516        log.debug( 'agent%d-max: %d' % ( x, agent_max ) )
517        log.debug("OID_MARKER_SUPPLIES_COLORANT_INDEX_%d:" % x)
518        oid = ( pml.OID_MARKER_SUPPLIES_COLORANT_INDEX_x % x, pml.OID_MARKER_SUPPLIES_COLORANT_INDEX_x_TYPE )
519        result_code, colorant_index = dev.getPML( oid )
520
521        if result_code != ERROR_SUCCESS: # 3080, 3055 will fail here
522            log.debug("Failed")
523            agent_type = AGENT_TYPE_BLACK
524            #break
525        else:
526            log.debug("Colorant index: %d" % colorant_index)
527
528            log.debug("OID_MARKER_COLORANT_VALUE_%d" % x)
529            oid = ( pml.OID_MARKER_COLORANT_VALUE_x % colorant_index, pml.OID_MARKER_COLORANT_VALUE_x_TYPE )
530            result_code, colorant_value = dev.getPML( oid )
531
532            if result_code != ERROR_SUCCESS:
533                log.debug("Failed. Defaulting to black.")
534                agent_type = AGENT_TYPE_BLACK
535            #else:
536            if 1:
537                if agent_kind in (AGENT_KIND_MAINT_KIT, AGENT_KIND_ADF_KIT,
538                                  AGENT_KIND_DRUM_KIT, AGENT_KIND_TRANSFER_KIT):
539
540                    agent_type = AGENT_TYPE_UNSPECIFIED
541
542                else:
543                    agent_type = AGENT_TYPE_BLACK
544
545                    if result_code != ERROR_SUCCESS:
546                        log.debug("OID_MARKER_SUPPLIES_DESCRIPTION_%d:" % x)
547                        oid = (pml.OID_MARKER_SUPPLIES_DESCRIPTION_x % x, pml.OID_MARKER_SUPPLIES_DESCRIPTION_x_TYPE)
548                        result_code, colorant_value = dev.getPML( oid )
549
550                        if result_code != ERROR_SUCCESS:
551                            log.debug("Failed")
552                            break
553
554                        if colorant_value is not None:
555                            log.debug("colorant value: %s" % colorant_value)
556                            colorant_value = colorant_value.lower().strip()
557
558                            for c in COLORANT_INDEX_TO_AGENT_TYPE_MAP:
559                                if colorant_value.find(c) >= 0:
560                                    agent_type = COLORANT_INDEX_TO_AGENT_TYPE_MAP[c]
561                                    break
562                            else:
563                                agent_type = AGENT_TYPE_BLACK
564
565                    else: # SUCCESS
566                        if colorant_value is not None:
567                            log.debug("colorant value: %s" % colorant_value)
568                            colorant_value = colorant_value.lower().strip()
569                            agent_type = COLORANT_INDEX_TO_AGENT_TYPE_MAP.get( colorant_value, AGENT_TYPE_BLACK )
570
571                        if agent_type == AGENT_TYPE_NONE:
572                            if agent_kind == AGENT_KIND_TONER_CARTRIDGE:
573                                agent_type = AGENT_TYPE_BLACK
574                            else:
575                                agent_type = AGENT_TYPE_UNSPECIFIED
576
577        log.debug("OID_MARKER_STATUS_%d:" % x)
578        oid = ( pml.OID_MARKER_STATUS_x % x, pml.OID_MARKER_STATUS_x_TYPE )
579        result_code, agent_status = dev.getPML( oid )
580
581        if result_code != ERROR_SUCCESS:
582            log.debug("Failed")
583            agent_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
584            agent_health = AGENT_HEALTH_OK
585        else:
586            agent_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
587
588            if agent_status is None:
589                agent_health = AGENT_HEALTH_OK
590
591            elif agent_status == pml.OID_MARKER_STATUS_OK:
592                agent_health = AGENT_HEALTH_OK
593
594            elif agent_status == pml.OID_MARKER_STATUS_MISINSTALLED:
595                agent_health = AGENT_HEALTH_MISINSTALLED
596
597            elif agent_status in ( pml.OID_MARKER_STATUS_LOW_TONER_CONT,
598                                   pml.OID_MARKER_STATUS_LOW_TONER_STOP ):
599
600                agent_health = AGENT_HEALTH_OK
601                agent_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
602
603            else:
604                agent_health = AGENT_HEALTH_OK
605
606        agent_level = int(float(agent_level)/agent_max * 100)
607
608
609        log.debug("agent%d: kind=%d, type=%d, health=%d, level=%d, level-trigger=%d" % \
610            (x, agent_kind, agent_type, agent_health, agent_level, agent_trigger))
611
612
613        agents.append({'kind' : agent_kind,
614                       'type' : agent_type,
615                       'health' : agent_health,
616                       'level' : agent_level,
617                       'level-trigger' : agent_trigger,})
618
619        x += 1
620
621        if x > 20:
622            break
623
624
625    printer_status = printer_status or STATUS_PRINTER_IDLE
626    log.debug("printer_status=%d" % printer_status)
627    device_status = device_status or pml.DEVICE_STATUS_RUNNING
628    log.debug("device_status=%d" % device_status)
629    cover_status = cover_status or pml.COVER_STATUS_CLOSED
630    log.debug("cover_status=%d" % cover_status)
631    detected_error_state = detected_error_state or pml.DETECTED_ERROR_STATE_NO_ERROR
632    log.debug("detected_error_state=%d (0x%x)" % (detected_error_state, detected_error_state))
633
634    stat = LaserJetDeviceStatusToPrinterStatus(device_status, printer_status, detected_error_state)
635
636    log.debug("Printer status=%d" % stat)
637
638    if stat == STATUS_PRINTER_DOOR_OPEN:
639        supply_door = 0
640    else:
641        supply_door = 1
642
643    return {'revision' :    STATUS_REV_UNKNOWN,
644             'agents' :      agents,
645             'top-door' :    cover_status,
646             'status-code' : stat,
647             'supply-door' : supply_door,
648             'duplexer' :    1,
649             'photo-tray' :  0,
650             'in-tray1' :    1,
651             'in-tray2' :    1,
652             'media-path' :  1,
653           }
654
655def setup_panel_translator():
656    printables = list(
657"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
658!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~""")
659
660    map = {}
661    for x in [chr(x) for x in range(0,256)]:
662        if x in printables:
663            map[x] = x
664        else:
665            map[x] = '\x20'
666
667    map.update({'\x10' : '\xab',
668                    '\x11' : '\xbb',
669                    '\x12' : '\xa3',
670                    '\x13' : '\xbb',
671                    '\x80' : '\xab',
672                    '\x81' : '\xbb',
673                    '\x82' : '\x2a',
674                    '\x83' : '\x2a',
675                    '\x85' : '\x2a',
676                    '\xa0' : '\xab',
677                    '\x1f' : '\x3f',
678                    '='    : '\x20',
679                })
680
681    frm, to = to_bytes_latin(''), to_bytes_latin('')
682    map_keys = list(map.keys())
683    map_keys.sort()
684    for x in map_keys:
685        frm = to_bytes_latin('').join([frm, to_bytes_latin(x)])
686        to = to_bytes_latin('').join([to, to_bytes_latin(map[x])])
687
688    global PANEL_TRANSLATOR_FUNC
689    PANEL_TRANSLATOR_FUNC = utils.Translator(frm, to)
690
691PANEL_TRANSLATOR_FUNC = None
692setup_panel_translator()
693
694
695def PanelCheck(dev):
696    line1, line2 = to_bytes_utf8(''), ('')
697
698    if dev.io_mode not in (IO_MODE_RAW, IO_MODE_UNI):
699
700        try:
701            dev.openPML()
702        except Error:
703            pass
704        else:
705
706            oids = [(pml.OID_HP_LINE1, pml.OID_HP_LINE2),
707                     (pml.OID_SPM_LINE1, pml.OID_SPM_LINE2)]
708
709            for oid1, oid2 in oids:
710                result, line1 = dev.getPML(oid1)
711
712                if result < pml.ERROR_MAX_OK:
713                    line1 = PANEL_TRANSLATOR_FUNC(line1.encode('utf-8')).rstrip()
714
715                    if to_bytes_utf8('\x0a') in line1:
716                        line1, line2 = line1.split(to_bytes_utf8('\x0a'), 1)
717                        break
718
719                    result, line2 = dev.getPML(oid2)
720
721                    if result < pml.ERROR_MAX_OK:
722                        line2 = PANEL_TRANSLATOR_FUNC(line2.encode('utf-8')).rstrip()
723                        break
724
725    return bool(line1 or line2), line1 or to_bytes_utf8(''), line2 or to_bytes_utf8('')
726
727
728BATTERY_HEALTH_MAP = {0 : AGENT_HEALTH_OK,
729                       1 : AGENT_HEALTH_OVERTEMP,
730                       2 : AGENT_HEALTH_CHARGING,
731                       3 : AGENT_HEALTH_MISINSTALLED,
732                       4 : AGENT_HEALTH_FAILED,
733                      }
734
735
736BATTERY_TRIGGER_MAP = {0 : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
737                        1 : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
738                        2 : AGENT_LEVEL_TRIGGER_PROBABLY_OUT,
739                        3 : AGENT_LEVEL_TRIGGER_SUFFICIENT_4,
740                        4 : AGENT_LEVEL_TRIGGER_SUFFICIENT_2,
741                        5 : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
742                       }
743
744BATTERY_PML_TRIGGER_MAP = {
745        (100, 80)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
746        (79,  60)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_1,
747        (59,  40)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_2,
748        (39,  30)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_3,
749        (29,  20)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_4,
750        (19,  10)  : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
751        (9,    5)  : AGENT_LEVEL_TRIGGER_PROBABLY_OUT,
752        (4,   -1)  : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
753        }
754
755
756def BatteryCheck(dev, status_block, battery_check):
757    try_dynamic_counters = False
758
759    try:
760        try:
761            dev.openPML()
762        except Error:
763            if battery_check == STATUS_BATTERY_CHECK_STD:
764                log.debug("PML channel open failed. Trying dynamic counters...")
765                try_dynamic_counters = True
766        else:
767            if battery_check == STATUS_BATTERY_CHECK_PML:
768                result, battery_level = dev.getPML(pml.OID_BATTERY_LEVEL_2)
769
770                if result > pml.ERROR_MAX_OK:
771                    status_block['agents'].append({
772                        'kind'   : AGENT_KIND_INT_BATTERY,
773                        'type'   : AGENT_TYPE_UNSPECIFIED,
774                        'health' : AGENT_HEALTH_UNKNOWN,
775                        'level'  : 0,
776                        'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
777                        })
778                    return
779
780                else:
781                    status_block['agents'].append({
782                        'kind'   : AGENT_KIND_INT_BATTERY,
783                        'type'   : AGENT_TYPE_UNSPECIFIED,
784                        'health' : AGENT_HEALTH_OK,
785                        'level'  : battery_level,
786                        'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
787                        })
788                    return
789
790            else: # STATUS_BATTERY_CHECK_STD
791                result, battery_level = dev.getPML(pml.OID_BATTERY_LEVEL)
792                result, power_mode =  dev.getPML(pml.OID_POWER_MODE)
793
794                if battery_level is not None and \
795                    power_mode is not None:
796
797                    if power_mode & pml.POWER_MODE_BATTERY_LEVEL_KNOWN and \
798                        battery_level >= 0:
799
800                        for x in BATTERY_PML_TRIGGER_MAP:
801                            if x[0] >= battery_level > x[1]:
802                                battery_trigger_level = BATTERY_PML_TRIGGER_MAP[x]
803                                break
804
805                        if power_mode & pml.POWER_MODE_CHARGING:
806                            agent_health = AGENT_HEALTH_CHARGING
807
808                        elif power_mode & pml.POWER_MODE_DISCHARGING:
809                            agent_health = AGENT_HEALTH_DISCHARGING
810
811                        else:
812                            agent_health = AGENT_HEALTH_OK
813
814                        status_block['agents'].append({
815                            'kind'   : AGENT_KIND_INT_BATTERY,
816                            'type'   : AGENT_TYPE_UNSPECIFIED,
817                            'health' : agent_health,
818                            'level'  : battery_level,
819                            'level-trigger' : battery_trigger_level,
820                            })
821                        return
822
823                    else:
824                        status_block['agents'].append({
825                            'kind'   : AGENT_KIND_INT_BATTERY,
826                            'type'   : AGENT_TYPE_UNSPECIFIED,
827                            'health' : AGENT_HEALTH_UNKNOWN,
828                            'level'  : 0,
829                            'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
830                            })
831                        return
832
833                else:
834                    try_dynamic_counters = True
835
836    finally:
837        dev.closePML()
838
839
840    if battery_check == STATUS_BATTERY_CHECK_STD and \
841        try_dynamic_counters:
842
843        try:
844            try:
845                battery_health = dev.getDynamicCounter(200)
846                battery_trigger_level = dev.getDynamicCounter(201)
847                battery_level = dev.getDynamicCounter(202)
848
849                status_block['agents'].append({
850                    'kind'   : AGENT_KIND_INT_BATTERY,
851                    'type'   : AGENT_TYPE_UNSPECIFIED,
852                    'health' : BATTERY_HEALTH_MAP[battery_health],
853                    'level'  : battery_level,
854                    'level-trigger' : BATTERY_TRIGGER_MAP[battery_trigger_level],
855                    })
856            except Error:
857                status_block['agents'].append({
858                    'kind'   : AGENT_KIND_INT_BATTERY,
859                    'type'   : AGENT_TYPE_UNSPECIFIED,
860                    'health' : AGENT_HEALTH_UNKNOWN,
861                    'level'  : 0,
862                    'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
863                    })
864        finally:
865            dev.closePrint()
866
867    else:
868        status_block['agents'].append({
869            'kind'   : AGENT_KIND_INT_BATTERY,
870            'type'   : AGENT_TYPE_UNSPECIFIED,
871            'health' : AGENT_HEALTH_UNKNOWN,
872            'level'  : 0,
873            'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
874            })
875
876
877
878# this works for 2 pen products that allow 1 or 2 pens inserted
879# from: k, kcm, cmy, ggk
880def getPenConfiguration(s): # s=status dict from parsed device ID
881    pens = [p['type'] for p in s['agents']]
882
883    if utils.all(pens, lambda x : x==AGENT_TYPE_NONE):
884        return AGENT_CONFIG_NONE
885
886    if AGENT_TYPE_NONE in pens:
887
888        if AGENT_TYPE_BLACK in pens:
889            return AGENT_CONFIG_BLACK_ONLY
890
891        elif AGENT_TYPE_CMY in pens:
892            return AGENT_CONFIG_COLOR_ONLY
893
894        elif AGENT_TYPE_KCM in pens:
895            return AGENT_CONFIG_PHOTO_ONLY
896
897        elif AGENT_TYPE_GGK in pens:
898            return AGENT_CONFIG_GREY_ONLY
899
900        else:
901            return AGENT_CONFIG_INVALID
902
903    else:
904        if AGENT_TYPE_BLACK in pens and AGENT_TYPE_CMY in pens:
905            return AGENT_CONFIG_COLOR_AND_BLACK
906
907        elif AGENT_TYPE_CMY in pens and AGENT_TYPE_KCM in pens:
908            return AGENT_CONFIG_COLOR_AND_PHOTO
909
910        elif AGENT_TYPE_CMY in pens and AGENT_TYPE_GGK in pens:
911            return AGENT_CONFIG_COLOR_AND_GREY
912
913        else:
914            return AGENT_CONFIG_INVALID
915
916
917def getFaxStatus(dev):
918    tx_active, rx_active = False, False
919
920    if dev.io_mode not in (IO_MODE_UNI, IO_MODE_RAW):
921        try:
922            dev.openPML()
923
924            result_code, tx_state = dev.getPML(pml.OID_FAXJOB_TX_STATUS)
925
926            if result_code == ERROR_SUCCESS and tx_state:
927                if tx_state not in (pml.FAXJOB_TX_STATUS_IDLE, pml.FAXJOB_TX_STATUS_DONE):
928                    tx_active = True
929
930            result_code, rx_state = dev.getPML(pml.OID_FAXJOB_RX_STATUS)
931
932            if result_code == ERROR_SUCCESS and rx_state:
933                if rx_state not in (pml.FAXJOB_RX_STATUS_IDLE, pml.FAXJOB_RX_STATUS_DONE):
934                    rx_active = True
935
936        finally:
937            dev.closePML()
938
939    return tx_active, rx_active
940
941
942TYPE6_STATUS_CODE_MAP = {
943     0    : STATUS_PRINTER_IDLE, #</DevStatusUnknown>
944    -19928: STATUS_PRINTER_IDLE,
945    -18995: STATUS_PRINTER_CANCELING,
946    -17974: STATUS_PRINTER_WARMING_UP,
947    -17973: STATUS_PRINTER_PEN_CLEANING, # sic
948    -18993: STATUS_PRINTER_BUSY,
949    -17949: STATUS_PRINTER_BUSY,
950    -19720: STATUS_PRINTER_MANUAL_DUPLEX_BLOCK,
951    -19678: STATUS_PRINTER_BUSY,
952    -19695: STATUS_PRINTER_OUT_OF_PAPER,
953    -17985: STATUS_PRINTER_MEDIA_JAM,
954    -19731: STATUS_PRINTER_OUT_OF_PAPER,
955    -18974: STATUS_PRINTER_BUSY, #?
956    -19730: STATUS_PRINTER_OUT_OF_PAPER,
957    -19729: STATUS_PRINTER_OUT_OF_PAPER,
958    -19933: STATUS_PRINTER_HARD_ERROR, # out of memory
959    -17984: STATUS_PRINTER_DOOR_OPEN,
960    -19694: STATUS_PRINTER_DOOR_OPEN,
961    -18992: STATUS_PRINTER_MANUAL_FEED_BLOCKED, # ?
962    -19690: STATUS_PRINTER_MEDIA_JAM, # tray 1
963    -19689: STATUS_PRINTER_MEDIA_JAM, # tray 2
964    -19611: STATUS_PRINTER_MEDIA_JAM, # tray 3
965    -19686: STATUS_PRINTER_MEDIA_JAM,
966    -19688: STATUS_PRINTER_MEDIA_JAM, # paper path
967    -19685: STATUS_PRINTER_MEDIA_JAM, # cart area
968    -19684: STATUS_PRINTER_MEDIA_JAM, # output bin
969    -18848: STATUS_PRINTER_MEDIA_JAM, # duplexer
970    -18847: STATUS_PRINTER_MEDIA_JAM, # door open
971    -18846: STATUS_PRINTER_MEDIA_JAM, # tray 2
972    -19687: STATUS_PRINTER_MEDIA_JAM, # open door
973    -17992: STATUS_PRINTER_MEDIA_JAM, # mispick
974    -19700: STATUS_PRINTER_HARD_ERROR, # invalid driver
975    -17996: STATUS_PRINTER_FUSER_ERROR, # fuser error
976    -17983: STATUS_PRINTER_FUSER_ERROR,
977    -17982: STATUS_PRINTER_FUSER_ERROR,
978    -17981: STATUS_PRINTER_FUSER_ERROR,
979    -17971: STATUS_PRINTER_FUSER_ERROR,
980    -17995: STATUS_PRINTER_HARD_ERROR, # beam error
981    -17994: STATUS_PRINTER_HARD_ERROR, # scanner error
982    -17993: STATUS_PRINTER_HARD_ERROR, # fan error
983    -18994: STATUS_PRINTER_HARD_ERROR,
984    -17986: STATUS_PRINTER_HARD_ERROR,
985    -19904: STATUS_PRINTER_HARD_ERROR,
986    -19701: STATUS_PRINTER_NON_HP_INK, # [sic]
987    -19613: STATUS_PRINTER_IDLE, # HP
988    -19654: STATUS_PRINTER_NON_HP_INK, # [sic]
989    -19682: STATUS_PRINTER_HARD_ERROR, # resinstall
990    -19693: STATUS_PRINTER_IDLE, # ?? To Accept
991    -19752: STATUS_PRINTER_LOW_TONER,
992    -19723: STATUS_PRINTER_BUSY,
993    -19703: STATUS_PRINTER_BUSY,
994    -19739: STATUS_PRINTER_NO_TONER,
995    -19927: STATUS_PRINTER_BUSY,
996    -19932: STATUS_PRINTER_BUSY,
997    -19931: STATUS_PRINTER_BUSY,
998    -11989: STATUS_PRINTER_BUSY,
999    -11995: STATUS_PRINTER_BUSY, # ADF loaded
1000    -19954: STATUS_PRINTER_CANCELING,
1001    -19955: STATUS_PRINTER_REPORT_PRINTING,
1002    -19956: STATUS_PRINTER_REPORT_PRINTING,
1003    -19934: STATUS_PRINTER_HARD_ERROR,
1004    -19930: STATUS_PRINTER_BUSY,
1005    -11990: STATUS_PRINTER_DOOR_OPEN,
1006    -11999: STATUS_PRINTER_MEDIA_JAM, # ADF
1007    -12000: STATUS_PRINTER_MEDIA_JAM, # ADF
1008    -11998: STATUS_PRINTER_MEDIA_JAM, # ADF
1009    -11986: STATUS_PRINTER_HARD_ERROR, # scanner
1010    -11994: STATUS_PRINTER_BUSY,
1011    -14967: STATUS_PRINTER_BUSY,
1012    -19912: STATUS_PRINTER_HARD_ERROR,
1013    -14962: STATUS_PRINTER_BUSY, # copy pending
1014    -14971: STATUS_PRINTER_BUSY, # copying
1015    -14973: STATUS_PRINTER_BUSY, # copying being canceled
1016    -14972: STATUS_PRINTER_BUSY, # copying canceled
1017    -14966: STATUS_PRINTER_DOOR_OPEN,
1018    -14974: STATUS_PRINTER_MEDIA_JAM,
1019    -14969: STATUS_PRINTER_HARD_ERROR,
1020    -14968: STATUS_PRINTER_HARD_ERROR,
1021    -12996: STATUS_PRINTER_BUSY, # scan
1022    -12994: STATUS_PRINTER_BUSY, # scan
1023    -12993: STATUS_PRINTER_BUSY, # scan
1024    -12991: STATUS_PRINTER_BUSY, # scan
1025    -12995: STATUS_PRINTER_BUSY, # scan
1026    -12997: STATUS_PRINTER_HARD_ERROR, # scan
1027    -12990: STATUS_PRINTER_BUSY,
1028    -12998: STATUS_PRINTER_BUSY,
1029    -13000: STATUS_PRINTER_DOOR_OPEN,
1030    -12999: STATUS_PRINTER_MEDIA_JAM,
1031    -13859: STATUS_PRINTER_BUSY,
1032    -13858: STATUS_PRINTER_BUSY, #</DevStatusDialingOut>
1033    -13868: STATUS_PRINTER_BUSY, #</DevStatusRedialPending>
1034    -13867: STATUS_PRINTER_BUSY, #</DevStatusFaxSendCanceled>
1035    -13857: STATUS_PRINTER_BUSY, #</DevStatusConnecting>
1036    -13856: STATUS_PRINTER_BUSY, #</DevStatusSendingPage>
1037    -13855: STATUS_PRINTER_BUSY, #</DevStatusOnePageSend>
1038    -13854: STATUS_PRINTER_BUSY, #</DevStatusMultiplePagesSent>
1039    -13853: STATUS_PRINTER_BUSY, #</DevStatusSenderCancelingFax>
1040    -13839: STATUS_PRINTER_BUSY, #</DevStatusIncomingCall>
1041    -13842: STATUS_PRINTER_BUSY, #</DevStatusBlockingFax>
1042    -13838: STATUS_PRINTER_BUSY, #</DevStatusReceivingFax>
1043    -13847: STATUS_PRINTER_BUSY, #</DevStatusSinglePageReceived>
1044    -13846: STATUS_PRINTER_BUSY, #</DevStatusDoublePagesReceived>
1045    -13845: STATUS_PRINTER_BUSY, #</DevStatusTriplePagesReceived>
1046    -13844: STATUS_PRINTER_BUSY, #</DevStatusPrintingFax>
1047    -13840: STATUS_PRINTER_BUSY, #</DevStatusCancelingFaxPrint>
1048    -13843: STATUS_PRINTER_BUSY, #</DevStatusFaxCancelingReceive>
1049    -13850: STATUS_PRINTER_BUSY, #</DevStatusFaxCanceledReceive>
1050    -13851: STATUS_PRINTER_BUSY, #</DevStatusFaxDelayedSendMemoryFull>
1051    -13836: STATUS_PRINTER_BUSY, #</DevStatusNoDialTone>
1052    -13864: STATUS_PRINTER_BUSY, #</DevStatusNoFaxAnswer>
1053    -13863: STATUS_PRINTER_BUSY, #</DevStatusFaxBusy>
1054    -13865: STATUS_PRINTER_BUSY, #</DevStatusNoDocumentSent>
1055    -13862: STATUS_PRINTER_BUSY, #</DevStatusFaxSendError>
1056    -13837: STATUS_PRINTER_BUSY, #</DevStatusT30Error>
1057    -13861: STATUS_PRINTER_BUSY, #</DevStatusFaxMemoryFullSend>
1058    -13866: STATUS_PRINTER_BUSY, #</DevStatusADFNotCleared>
1059    -13841: STATUS_PRINTER_BUSY, #</DevStatusNoFaxDetected>
1060    -13848: STATUS_PRINTER_BUSY, #</DevStatusFaxMemoryFullReceive>
1061    -13849: STATUS_PRINTER_BUSY, #</DevStatusFaxReceiveError>
1062
1063}
1064
1065def StatusType6(dev): #  LaserJet Status (XML)
1066    info_device_status = BytesIO()
1067    info_ssp = BytesIO()
1068    try:
1069        dev.getEWSUrl("/hp/device/info_device_status.xml", info_device_status)
1070        dev.getEWSUrl("/hp/device/info_ssp.xml", info_ssp)
1071    except:
1072        log.warn("Failed to get Device status information")
1073        pass
1074
1075    info_device_status = info_device_status.getvalue()
1076    info_ssp = info_ssp.getvalue()
1077
1078    device_status = {}
1079    ssp = {}
1080
1081    if info_device_status:
1082        try:
1083            log.debug_block("info_device_status", to_string_latin(info_device_status))
1084            device_status = utils.XMLToDictParser().parseXML(info_device_status)
1085            log.debug(device_status)
1086        except expat.ExpatError:
1087            log.error("Device Status XML parse error")
1088            device_status = {}
1089
1090    if info_ssp:
1091        try:
1092            log.debug_block("info_spp", to_string_latin(info_ssp))
1093            ssp = utils.XMLToDictParser().parseXML(info_ssp)
1094            log.debug(ssp)
1095        except expat.ExpatError:
1096            log.error("SSP XML parse error")
1097            ssp = {}
1098
1099    status_code = device_status.get('devicestatuspage-devicestatus-statuslist-status-code-0', 0)
1100
1101    if not status_code:
1102        status_code = ssp.get('devicestatuspage-devicestatus-statuslist-status-code-0', 0)
1103
1104    black_supply_level = device_status.get('devicestatuspage-suppliesstatus-blacksupply-percentremaining', 0)
1105    black_supply_low = ssp.get('suppliesstatuspage-blacksupply-lowreached', 0)
1106    agents = []
1107
1108    agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1109                     'type' : AGENT_TYPE_BLACK,
1110                     'health' : 0,
1111                     'level' : black_supply_level,
1112                     'level-trigger' : 0,
1113                  })
1114
1115    if dev.tech_type == TECH_TYPE_COLOR_LASER:
1116        cyan_supply_level = device_status.get('devicestatuspage-suppliesstatus-cyansupply-percentremaining', 0)
1117        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1118                         'type' : AGENT_TYPE_CYAN,
1119                         'health' : 0,
1120                         'level' : cyan_supply_level,
1121                         'level-trigger' : 0,
1122                      })
1123
1124        magenta_supply_level = device_status.get('devicestatuspage-suppliesstatus-magentasupply-percentremaining', 0)
1125        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1126                         'type' : AGENT_TYPE_MAGENTA,
1127                         'health' : 0,
1128                         'level' : magenta_supply_level,
1129                         'level-trigger' : 0,
1130                      })
1131
1132        yellow_supply_level = device_status.get('devicestatuspage-suppliesstatus-yellowsupply-percentremaining', 0)
1133        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1134                         'type' : AGENT_TYPE_YELLOW,
1135                         'health' : 0,
1136                         'level' : yellow_supply_level,
1137                         'level-trigger' : 0,
1138                      })
1139
1140    return {'revision' :    STATUS_REV_UNKNOWN,
1141             'agents' :      agents,
1142             'top-door' :    0,
1143             'supply-door' : 0,
1144             'duplexer' :    1,
1145             'photo-tray' :  0,
1146             'in-tray1' :    1,
1147             'in-tray2' :    1,
1148             'media-path' :  1,
1149             'status-code' : TYPE6_STATUS_CODE_MAP.get(status_code, STATUS_PRINTER_IDLE),
1150           }
1151
1152# PJL status codes
1153PJL_STATUS_MAP = {
1154    10001: STATUS_PRINTER_IDLE, # online
1155    10002: STATUS_PRINTER_OFFLINE, # offline
1156    10003: STATUS_PRINTER_WARMING_UP,
1157    10004: STATUS_PRINTER_BUSY, # self test
1158    10005: STATUS_PRINTER_BUSY, # reset
1159    10006: STATUS_PRINTER_LOW_TONER,
1160    10007: STATUS_PRINTER_CANCELING,
1161    10010: STATUS_PRINTER_SERVICE_REQUEST,
1162    10011: STATUS_PRINTER_OFFLINE,
1163    10013: STATUS_PRINTER_BUSY,
1164    10014: STATUS_PRINTER_REPORT_PRINTING,
1165    10015: STATUS_PRINTER_BUSY,
1166    10016: STATUS_PRINTER_BUSY,
1167    10017: STATUS_PRINTER_REPORT_PRINTING,
1168    10018: STATUS_PRINTER_BUSY,
1169    10019: STATUS_PRINTER_BUSY,
1170    10020: STATUS_PRINTER_BUSY,
1171    10021: STATUS_PRINTER_BUSY,
1172    10022: STATUS_PRINTER_REPORT_PRINTING,
1173    10023: STATUS_PRINTER_PRINTING,
1174    10024: STATUS_PRINTER_SERVICE_REQUEST,
1175    10025: STATUS_PRINTER_SERVICE_REQUEST,
1176    10026: STATUS_PRINTER_BUSY,
1177    10027: STATUS_PRINTER_MEDIA_JAM,
1178    10028: STATUS_PRINTER_REPORT_PRINTING,
1179    10029: STATUS_PRINTER_PRINTING,
1180    10030: STATUS_PRINTER_BUSY,
1181    10031: STATUS_PRINTER_BUSY,
1182    10032: STATUS_PRINTER_BUSY,
1183    10033: STATUS_PRINTER_SERVICE_REQUEST,
1184    10034: STATUS_PRINTER_CANCELING,
1185    10035: STATUS_PRINTER_PRINTING,
1186    10036: STATUS_PRINTER_WARMING_UP,
1187    10200: STATUS_PRINTER_LOW_BLACK_TONER,
1188    10201: STATUS_PRINTER_LOW_CYAN_TONER,
1189    10202: STATUS_PRINTER_LOW_MAGENTA_TONER,
1190    10203: STATUS_PRINTER_LOW_YELLOW_TONER,
1191    10204: STATUS_PRINTER_LOW_TONER, # order image drum
1192    10205: STATUS_PRINTER_LOW_BLACK_TONER, # order black drum
1193    10206: STATUS_PRINTER_LOW_CYAN_TONER, # order cyan drum
1194    10207: STATUS_PRINTER_LOW_MAGENTA_TONER, # order magenta drum
1195    10208: STATUS_PRINTER_LOW_YELLOW_TONER, # order yellow drum
1196    10209: STATUS_PRINTER_LOW_BLACK_TONER,
1197    10210: STATUS_PRINTER_LOW_CYAN_TONER,
1198    10211: STATUS_PRINTER_LOW_MAGENTA_TONER,
1199    10212: STATUS_PRINTER_LOW_YELLOW_TONER,
1200    10213: STATUS_PRINTER_SERVICE_REQUEST, # order transport kit
1201    10214: STATUS_PRINTER_SERVICE_REQUEST, # order cleaning kit
1202    10215: STATUS_PRINTER_SERVICE_REQUEST, # order transfer kit
1203    10216: STATUS_PRINTER_SERVICE_REQUEST, # order fuser kit
1204    10217: STATUS_PRINTER_SERVICE_REQUEST, # maintenance
1205    10218: STATUS_PRINTER_LOW_TONER,
1206    10300: STATUS_PRINTER_LOW_BLACK_TONER, # replace black toner
1207    10301: STATUS_PRINTER_LOW_CYAN_TONER, # replace cyan toner
1208    10302: STATUS_PRINTER_LOW_MAGENTA_TONER, # replace magenta toner
1209    10303: STATUS_PRINTER_LOW_YELLOW_TONER, # replace yellow toner
1210    10304: STATUS_PRINTER_SERVICE_REQUEST, # replace image drum
1211    10305: STATUS_PRINTER_SERVICE_REQUEST, # replace black drum
1212    10306: STATUS_PRINTER_SERVICE_REQUEST, # replace cyan drum
1213    10307: STATUS_PRINTER_SERVICE_REQUEST, # replace magenta drum
1214    10308: STATUS_PRINTER_SERVICE_REQUEST, # replace yellow drum
1215    10309: STATUS_PRINTER_SERVICE_REQUEST, # replace black cart
1216    10310: STATUS_PRINTER_SERVICE_REQUEST, # replace cyan cart
1217    10311: STATUS_PRINTER_SERVICE_REQUEST, # replace magenta cart
1218    10312: STATUS_PRINTER_SERVICE_REQUEST, # replace yellow cart
1219    10313: STATUS_PRINTER_SERVICE_REQUEST, # replace transport kit
1220    10314: STATUS_PRINTER_SERVICE_REQUEST, # replace cleaning kit
1221    10315: STATUS_PRINTER_SERVICE_REQUEST, # replace transfer kit
1222    10316: STATUS_PRINTER_SERVICE_REQUEST, # replace fuser kit
1223    10317: STATUS_PRINTER_SERVICE_REQUEST,
1224    10318: STATUS_PRINTER_SERVICE_REQUEST, # replace supplies
1225    10400: STATUS_PRINTER_NON_HP_INK, # [sic]
1226    10401: STATUS_PRINTER_IDLE,
1227    10402: STATUS_PRINTER_SERVICE_REQUEST,
1228    10403: STATUS_PRINTER_IDLE,
1229    # 11xyy - Background paper-loading
1230    # 12xyy - Background paper-tray status
1231    # 15xxy - Output-bin status
1232    # 20xxx - PJL parser errors
1233    # 25xxx - PJL parser warnings
1234    # 27xxx - PJL semantic errors
1235    # 30xxx - Auto continuable conditions
1236    30119: STATUS_PRINTER_MEDIA_JAM,
1237    # 32xxx - PJL file system errors
1238    # 35xxx - Potential operator intervention conditions
1239    # 40xxx - Operator intervention conditions
1240    40021: STATUS_PRINTER_DOOR_OPEN,
1241    40022: STATUS_PRINTER_MEDIA_JAM,
1242    40038: STATUS_PRINTER_LOW_TONER,
1243    40600: STATUS_PRINTER_NO_TONER,
1244    # 41xyy - Foreground paper-loading messages
1245    # 43xyy - Optional paper handling device messages
1246    # 44xyy - LJ 4xxx/5xxx paper jam messages
1247    # 50xxx - Hardware errors
1248    # 55xxx - Personality errors
1249
1250}
1251
1252MIN_PJL_ERROR_CODE = 10001
1253DEFAULT_PJL_ERROR_CODE = 10001
1254
1255def MapPJLErrorCode(error_code, str_code=None):
1256    if error_code < MIN_PJL_ERROR_CODE:
1257        return STATUS_PRINTER_BUSY
1258
1259    if str_code is None:
1260        str_code = str(error_code)
1261
1262    if len(str_code) < 5:
1263        return STATUS_PRINTER_BUSY
1264
1265    status_code = PJL_STATUS_MAP.get(error_code, None)
1266
1267    if status_code is None:
1268        status_code = STATUS_PRINTER_BUSY
1269
1270        if 10999 < error_code < 12000: # 11xyy - Background paper-loading
1271            # x = tray #
1272            # yy = media code
1273            tray = int(str_code[2])
1274            media = int(str_code[3:])
1275            log.debug("Background paper loading for tray #%d" % tray)
1276            log.debug("Media code = %d" % media)
1277
1278        elif 11999 < error_code < 13000: # 12xyy - Background paper-tray status
1279            # x = tray #
1280            # yy = status code
1281            tray = int(str_code[2])
1282            status = int(str_code[3:])
1283            log.debug("Background paper tray status for tray #%d" % tray)
1284            log.debug("Status code = %d" % status)
1285
1286        elif 14999 < error_code < 16000: # 15xxy - Output-bin status
1287            # xx = output bin
1288            # y = status code
1289            bin = int(str_code[2:4])
1290            status = int(str_code[4])
1291            log.debug("Output bin full for bin #%d" % bin)
1292            status_code = STATUS_PRINTER_OUTPUT_BIN_FULL
1293
1294        elif 19999 < error_code < 28000: # 20xxx, 25xxx, 27xxx PJL errors
1295            status_code = STATUS_PRINTER_SERVICE_REQUEST
1296
1297        elif 29999 < error_code < 31000: # 30xxx - Auto continuable conditions
1298            log.debug("Auto continuation condition #%d" % error_code)
1299            status_code = STATUS_PRINTER_BUSY
1300
1301        elif 34999 < error_code < 36000: # 35xxx - Potential operator intervention conditions
1302            status_code = STATUS_PRINTER_SERVICE_REQUEST
1303
1304        elif 39999 < error_code < 41000: # 40xxx - Operator intervention conditions
1305            status_code = STATUS_PRINTER_SERVICE_REQUEST
1306
1307        elif 40999 < error_code < 42000: # 41xyy - Foreground paper-loading messages
1308            # x = tray
1309            # yy = media code
1310            tray = int(str_code[2])
1311            media = int(str_code[3:])
1312            log.debug("Foreground paper loading for tray #%d" % tray)
1313            log.debug("Media code = %d" % media)
1314            status_code = STATUS_PRINTER_OUT_OF_PAPER
1315
1316        elif 41999 < error_code < 43000:
1317            status_code = STATUS_PRINTER_MEDIA_JAM
1318
1319        elif 42999 < error_code < 44000: # 43xyy - Optional paper handling device messages
1320            status_code = STATUS_PRINTER_SERVICE_REQUEST
1321
1322        elif 43999 < error_code < 45000: # 44xyy - LJ 4xxx/5xxx paper jam messages
1323            status_code = STATUS_PRINTER_MEDIA_JAM
1324
1325        elif 49999 < error_code < 51000: # 50xxx - Hardware errors
1326            status_code = STATUS_PRINTER_HARD_ERROR
1327
1328        elif 54999 < error_code < 56000 : # 55xxx - Personality errors
1329            status_code = STATUS_PRINTER_HARD_ERROR
1330
1331    log.debug("Mapped PJL error code %d to status code %d" % (error_code, status_code))
1332    return status_code
1333
1334
1335pjl_code_pat = re.compile("""^CODE\s*=\s*(\d.*)$""", re.IGNORECASE)
1336
1337
1338
1339def StatusType8(dev): #  LaserJet PJL (B&W only)
1340    try:
1341        # Will error if printer is busy printing...
1342        dev.openPrint()
1343    except Error as e:
1344        log.warn(e.msg)
1345        status_code = STATUS_PRINTER_BUSY
1346    else:
1347        try:
1348            try:
1349                dev.writePrint(to_bytes_utf8("\x1b%-12345X@PJL INFO STATUS \r\n\x1b%-12345X"))
1350                pjl_return = dev.readPrint(1024, timeout=5, allow_short_read=True)
1351                dev.close()
1352
1353                log.debug_block("PJL return:", to_string_latin(pjl_return))
1354
1355                str_code = '10001'
1356
1357                for line in pjl_return.splitlines():
1358                    line = line.strip()
1359                    match = pjl_code_pat.match(line.decode('utf-8'))
1360
1361                    if match is not None:
1362                        str_code = match.group(1)
1363                        break
1364
1365                log.debug("Code = %s" % str_code)
1366
1367                try:
1368                    error_code = int(str_code)
1369                except ValueError:
1370                    error_code = DEFAULT_PJL_ERROR_CODE
1371
1372                log.debug("Error code = %d" % error_code)
1373
1374                status_code = MapPJLErrorCode(error_code, str_code)
1375            except Error:
1376                status_code = STATUS_PRINTER_HARD_ERROR
1377        finally:
1378            try:
1379                dev.closePrint()
1380            except Error:
1381                pass
1382
1383    agents = []
1384
1385    # TODO: Only handles mono lasers...
1386    if status_code in (STATUS_PRINTER_LOW_TONER, STATUS_PRINTER_LOW_BLACK_TONER):
1387        health = AGENT_HEALTH_OK
1388        level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
1389        level = 0
1390
1391    elif status_code == STATUS_PRINTER_NO_TONER:
1392        health = AGENT_HEALTH_MISINSTALLED
1393        level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
1394        level = 0
1395
1396    else:
1397        health = AGENT_HEALTH_OK
1398        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
1399        level = 100
1400
1401    log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))
1402
1403    agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1404                     'type' : AGENT_TYPE_BLACK,
1405                     'health' : health,
1406                     'level' : level,
1407                     'level-trigger' : level_trigger,
1408                  })
1409
1410    if dev.tech_type == TECH_TYPE_COLOR_LASER:
1411        level = 100
1412        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
1413        if status_code == STATUS_PRINTER_LOW_CYAN_TONER:
1414            level = 0
1415            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
1416
1417        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))
1418
1419        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1420                         'type' : AGENT_TYPE_CYAN,
1421                         'health' : AGENT_HEALTH_OK,
1422                         'level' : level,
1423                         'level-trigger' : level_trigger,
1424                      })
1425
1426        level = 100
1427        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
1428        if status_code == STATUS_PRINTER_LOW_MAGENTA_TONER:
1429            level = 0
1430            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
1431
1432        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))
1433
1434        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1435                         'type' : AGENT_TYPE_MAGENTA,
1436                         'health' : AGENT_HEALTH_OK,
1437                         'level' : level,
1438                         'level-trigger' : level_trigger,
1439                      })
1440
1441        level = 100
1442        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
1443        if status_code == STATUS_PRINTER_LOW_YELLOW_TONER:
1444            level = 0
1445            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
1446
1447        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))
1448
1449        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
1450                         'type' : AGENT_TYPE_YELLOW,
1451                         'health' : AGENT_HEALTH_OK,
1452                         'level' : level,
1453                         'level-trigger' : level_trigger,
1454                      })
1455
1456    if status_code == 40021:
1457        top_door = 0
1458    else:
1459        top_door = 1
1460
1461    log.debug("Status code = %d" % status_code)
1462
1463    return { 'revision' :    STATUS_REV_UNKNOWN,
1464             'agents' :      agents,
1465             'top-door' :    top_door,
1466             'supply-door' : top_door,
1467             'duplexer' :    0,
1468             'photo-tray' :  0,
1469             'in-tray1' :    1,
1470             'in-tray2' :    1,
1471             'media-path' :  1,
1472             'status-code' : status_code,
1473           }
1474
1475
1476element_type10_xlate = { 'ink' : AGENT_KIND_SUPPLY,
1477                         'rechargeableToner' : AGENT_KIND_TONER_CARTRIDGE,
1478                         'inkTank' : AGENT_KIND_SUPPLY,
1479                         'inkCartridge' : AGENT_KIND_HEAD_AND_SUPPLY,
1480                         'printhead' : AGENT_KIND_HEAD,
1481                         'toner' : AGENT_KIND_TONER_CARTRIDGE,
1482                         'tonerCartridge' : AGENT_KIND_TONER_CARTRIDGE,
1483                       }
1484
1485pen_type10_xlate = { 'pK' : AGENT_TYPE_PHOTO_BLACK,
1486                     'CMY' : AGENT_TYPE_CMY,
1487                     'M' : AGENT_TYPE_MAGENTA,
1488                     'C' : AGENT_TYPE_CYAN,
1489                     'Y' : AGENT_TYPE_YELLOW,
1490                     'K' : AGENT_TYPE_BLACK,
1491                     'G' : AGENT_TYPE_G,
1492                     'mK' : AGENT_TYPE_MATTE_BLACK,
1493                   }
1494
1495pen_level10_xlate = { 'ok' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
1496                      'low' : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
1497                      'out' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1498                      'empty' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1499                      'missing' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1500                      'unknown' : AGENT_LEVEL_UNKNOWN,
1501                    }
1502
1503pen_health10_xlate = { 'ok' : AGENT_HEALTH_OK,
1504                       'misinstalled' : AGENT_HEALTH_MISINSTALLED,
1505                       'missing' : AGENT_HEALTH_MISINSTALLED,
1506                       'unknown' : AGENT_HEALTH_UNKNOWN,
1507                     }
1508
1509
1510#ExtractXMLData will extract actual data from http response (Transfer-encoding:  chunked).
1511#For unchunked response it will not do anything.
1512def ExtractXMLData(data):
1513    if data[0:1] != b'<':
1514        size = -1
1515        temp = to_bytes_utf8("")
1516        while size:
1517            index = data.find(to_bytes_utf8('\r\n'))
1518            size = int(data[0:index+1], 16)
1519            temp = temp + data[index+2:index+2+size]
1520            data = data[index+2+size+2:len(data)]
1521        data = temp
1522    return data
1523
1524def StatusType10FetchUrl(func, url, footer=""):
1525    data_fp = BytesIO()
1526    if footer:
1527        data = func(url, data_fp, footer)
1528    else:
1529        data = func(url, data_fp)
1530        if data:
1531            while data.find(to_bytes_utf8('\r\n\r\n')) != -1:
1532                data = data.split(to_bytes_utf8('\r\n\r\n'), 1)[1]
1533                if not data.startswith(to_bytes_utf8("HTTP")):
1534                    break
1535
1536            if data:
1537                data = ExtractXMLData(data)
1538    return data
1539
1540def StatusType10(func): # Low End Data Model
1541    status_block = { 'revision' :    STATUS_REV_UNKNOWN,
1542                     'agents' :      [],
1543                     'top-door' :    TOP_DOOR_NOT_PRESENT,
1544                     'supply-door' : TOP_DOOR_NOT_PRESENT,
1545                     'duplexer' :    DUPLEXER_NOT_PRESENT,
1546                     'photo-tray' :  PHOTO_TRAY_NOT_PRESENT,
1547                     'in-tray1' :    IN_TRAY_NOT_PRESENT,
1548                     'in-tray2' :    IN_TRAY_NOT_PRESENT,
1549                     'media-path' :  MEDIA_PATH_NOT_PRESENT,
1550                     'status-code' : STATUS_PRINTER_IDLE,
1551                   }
1552
1553    if not etree_loaded and not elementtree_loaded:
1554        log.error("cannot get status for printer. please load ElementTree module")
1555        return status_block
1556
1557    status_block = StatusType10Agents(func)
1558
1559    temp_status_block = {}
1560    temp_status_block = StatusType10Media(func)
1561    status_block.update(temp_status_block)
1562
1563    temp_status_block = {}
1564    temp_status_block = StatusType10Status(func)
1565    status_block.update(temp_status_block)
1566
1567    return status_block
1568
1569
1570def StatusType10Agents(func): # Low End Data Model
1571    status_block = {}
1572    # Get the dynamic consumables configuration
1573    data = StatusType10FetchUrl(func, "/DevMgmt/ConsumableConfigDyn.xml")
1574    if not data:
1575        return status_block
1576    data = data.replace(to_bytes_utf8("ccdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("dd:"), to_bytes_utf8(""))
1577
1578    # Parse the agent status XML
1579    agents = []
1580    try:
1581        if etree_loaded:
1582            tree = ElementTree.XML(data)
1583        if not etree_loaded and elementtree_loaded:
1584            tree = XML(data)
1585        elements = tree.findall("ConsumableInfo")
1586        for e in elements:
1587            health = AGENT_HEALTH_OK
1588            ink_level = 0
1589            agent_sku = ''
1590            try:
1591                type = e.find("ConsumableTypeEnum").text
1592                state = e.find("ConsumableLifeState/ConsumableState").text
1593                quantityState = e.find("ConsumableLifeState/MeasuredQuantityState").text
1594
1595                # level
1596                if type == "ink" or type == "inkCartridge" or type == "toner" or type == "tonerCartridge" or type == "rechargeableToner" or type == "inkTank":
1597                    ink_type = e.find("ConsumableLabelCode").text
1598                    if state != "missing":
1599                        try:
1600                           ink_level = int(e.find("ConsumablePercentageLevelRemaining").text)
1601                           if ink_level == 0 and quantityState == 'unknown':
1602                                state = "unknown"
1603                           elif ink_level == 0:
1604                               state = "empty"
1605                           elif ink_level <=10:
1606                               state = "low"
1607
1608                           agent_sku = 'Unknown' #Initialize to unknown. IN some old devices, ConsumableSelectibilityNumber is not returned by device.
1609                        except:
1610                           ink_level = 0
1611                elif type == "printhead" or type == 'imageDrum':
1612                     continue; #No need of adding this agent.
1613                else:
1614                    ink_type = ''
1615                    if state == "ok":
1616                        ink_level = 100
1617
1618                try:
1619                    agent_sku = e.find("ProductNumber").text
1620                except:
1621                    try :
1622                        agent_sku = e.find("ConsumableSelectibilityNumber").text
1623                    except :
1624                        pass
1625
1626                log.debug("type '%s' state '%s' ink_type '%s' ink_level %d agent_sku = %s" % (type, state, ink_type, ink_level,agent_sku))
1627
1628                entry = { 'kind' : element_type10_xlate.get(type, AGENT_KIND_NONE),
1629                          'type' : pen_type10_xlate.get(ink_type, AGENT_TYPE_NONE),
1630                          'health' : pen_health10_xlate.get(state, AGENT_HEALTH_OK),
1631                          'level' : int(ink_level),
1632                          'level-trigger' : pen_level10_xlate.get(state, AGENT_LEVEL_TRIGGER_SUFFICIENT_0),
1633                          'agent-sku' : agent_sku
1634                        }
1635
1636                log.debug("%s" % entry)
1637                agents.append(entry)
1638            except AttributeError:
1639                log.debug("no value found for attribute")
1640    except (expat.ExpatError, UnboundLocalError):
1641        agents = []
1642    status_block['agents'] = agents
1643
1644    return status_block
1645
1646def StatusType10Media(func): # Low End Data Model
1647    status_block = {}
1648    # Get the media handling configuration
1649    data = StatusType10FetchUrl(func, "/DevMgmt/MediaHandlingDyn.xml")
1650    if not data:
1651        return status_block
1652    data = data.replace(to_bytes_utf8("mhdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("dd:"), to_bytes_utf8(""))
1653
1654    # Parse the media handling XML
1655    try:
1656        if etree_loaded:
1657            tree = ElementTree.XML(data)
1658        if not etree_loaded and elementtree_loaded:
1659            tree = XML(data)
1660        elements = tree.findall("InputTray")
1661    except (expat.ExpatError, UnboundLocalError):
1662        elements = []
1663    for e in elements:
1664        bin_name = e.find("InputBin").text
1665        if bin_name == "Tray1":
1666            status_block['in-tray1'] = IN_TRAY_PRESENT
1667        elif bin_name == "Tray2":
1668            status_block['in-tray2'] = IN_TRAY_PRESENT
1669        elif bin_name == "PhotoTray":
1670            status_block['photo-tray'] = PHOTO_TRAY_ENGAGED
1671
1672    try:
1673        elements = tree.findall("Accessories/MediaHandlingDeviceFunctionType")
1674    except UnboundLocalError:
1675        elements = []
1676    for e in elements:
1677        if e.text == "autoDuplexor":
1678            status_block['duplexer'] = DUPLEXER_DOOR_CLOSED
1679
1680    return status_block
1681
1682def StatusType10Status(func): # Low End Data Model
1683    status_block = {}
1684    # Get the product status
1685    data = StatusType10FetchUrl(func, "/DevMgmt/ProductStatusDyn.xml")
1686    if not data:
1687        return status_block
1688    data = data.replace(to_bytes_utf8("psdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("locid:"), to_bytes_utf8(""))
1689    data = data.replace(to_bytes_utf8("pscat:"), to_bytes_utf8("")).replace(to_bytes_utf8("dd:"), to_bytes_utf8("")).replace(to_bytes_utf8("ad:"), to_bytes_utf8(""))
1690
1691    # Parse the product status XML
1692    try:
1693        if etree_loaded:
1694            tree = ElementTree.XML(data)
1695        if not etree_loaded and elementtree_loaded:
1696            tree = XML(data)
1697        elements = tree.findall("Status/StatusCategory")
1698    except (expat.ExpatError, UnboundLocalError):
1699        elements = []
1700
1701    for e in elements:
1702
1703        if e.text == "processing":
1704            status_block['status-code'] = STATUS_PRINTER_PRINTING
1705        elif e.text == "ready":
1706            status_block['status-code'] = STATUS_PRINTER_IDLE
1707        elif e.text == "closeDoorOrCover":
1708            status_block['status-code'] = STATUS_PRINTER_DOOR_OPEN
1709        elif e.text == "shuttingDown":
1710            status_block['status-code'] = STATUS_PRINTER_TURNING_OFF
1711        elif e.text == "cancelJob":
1712            status_block['status-code'] = STATUS_PRINTER_CANCELING
1713        elif e.text == "trayEmptyOrOpen":
1714            status_block['status-code'] = STATUS_PRINTER_OUT_OF_PAPER
1715        elif e.text == "jamInPrinter":
1716            status_block['status-code'] = STATUS_PRINTER_MEDIA_JAM
1717        elif e.text == "hardError":
1718            status_block['status-code'] = STATUS_PRINTER_HARD_ERROR
1719        elif e.text == "outputBinFull":
1720            status_block['status-code'] = STATUS_PRINTER_OUTPUT_BIN_FULL
1721        elif e.text == "unexpectedSizeInTray" or e.text == "sizeMismatchInTray":
1722            status_block['status-code'] = STATUS_PRINTER_MEDIA_SIZE_MISMATCH
1723        elif e.text == "insertOrCloseTray2":
1724            status_block['status-code'] = STATUS_PRINTER_TRAY_2_MISSING
1725        elif e.text == "scannerError":
1726            status_block['status-code'] = EVENT_SCANNER_FAIL
1727        elif e.text == "scanProcessing":
1728            status_block['status-code'] = EVENT_START_SCAN_JOB
1729        elif e.text == "scannerAdfLoaded":
1730            status_block['status-code'] = EVENT_SCAN_ADF_LOADED
1731        elif e.text == "scanToDestinationNotSet":
1732            status_block['status-code'] = EVENT_SCAN_TO_DESTINATION_NOTSET
1733        elif e.text == "scanWaitingForPC":
1734            status_block['status-code'] = EVENT_SCAN_WAITING_FOR_PC
1735        elif e.text == "scannerAdfJam":
1736            status_block['status-code'] = EVENT_SCAN_ADF_JAM
1737        elif e.text == "scannerAdfDoorOpen":
1738            status_block['status-code'] = EVENT_SCAN_ADF_DOOR_OPEN
1739        elif e.text == "faxProcessing":
1740            status_block['status-code'] = EVENT_START_FAX_JOB
1741        elif e.text == "faxSending":
1742            status_block['status-code'] = STATUS_FAX_TX_ACTIVE
1743        elif e.text == "faxReceiving":
1744            status_block['status-code'] = STATUS_FAX_RX_ACTIVE
1745        elif e.text == "faxDialing":
1746            status_block['status-code'] = EVENT_FAX_DIALING
1747        elif e.text == "faxConnecting":
1748            status_block['status-code'] = EVENT_FAX_CONNECTING
1749        elif e.text == "faxSendError":
1750            status_block['status-code'] = EVENT_FAX_SEND_ERROR
1751        elif e.text == "faxErrorStorageFull":
1752            status_block['status-code'] = EVENT_FAX_ERROR_STORAGE_FULL
1753        elif e.text == "faxReceiveError":
1754            status_block['status-code'] = EVENT_FAX_RECV_ERROR
1755        elif e.text == "faxBlocking":
1756            status_block['status-code'] = EVENT_FAX_BLOCKING
1757        elif e.text == "inPowerSave":
1758            status_block['status-code'] = STATUS_PRINTER_POWER_SAVE
1759        elif e.text == "incorrectCartridge":
1760            status_block['status-code'] = STATUS_PRINTER_CARTRIDGE_WRONG
1761        elif e.text == "cartridgeMissing":
1762            status_block['status-code'] = STATUS_PRINTER_CARTRIDGE_MISSING
1763        elif e.text == "missingPrintHead":
1764            status_block['status-code'] = STATUS_PRINTER_PRINTHEAD_MISSING
1765
1766
1767        #Alert messages for Pentane products RQ 8888
1768        elif e.text == "scannerADFMispick":
1769            status_block['status-code'] = STATUS_SCANNER_ADF_MISPICK
1770
1771        elif e.text == "mediaTooShortToAutoDuplex":
1772            status_block['status-code'] = STATUS_PRINTER_PAPER_TOO_SHORT_TO_AUTODUPLEX
1773
1774        elif e.text == "insertOrCloseTray":
1775            status_block['status-code'] = STATUS_PRINTER_TRAY_2_3_DOOR_OPEN
1776
1777        elif e.text == "inkTooLowToPrime":
1778            status_block['status-code'] = STATUS_PRINTER_INK_TOO_LOW_TO_PRIME
1779
1780        elif e.text == "cartridgeVeryLow":
1781            status_block['status-code'] = STATUS_PRINTER_VERY_LOW_ON_INK
1782
1783        elif e.text == "wasteMarkerCollectorAlmostFull":
1784            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_ALMOST_FULL
1785
1786        elif e.text == "wasteMarkerCollectorFull":
1787            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_FULL
1788
1789        elif e.text == "wasteMarkerCollectorFullPrompt":
1790            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_FULL_PROMPT
1791
1792        elif e.text == "missingDuplexer":
1793            status_block['status-code'] = STATUS_PRINTER_DUPLEX_MODULE_MISSING
1794
1795        elif e.text == "printBarStall":
1796            status_block['status-code'] = STATUS_PRINTER_PRINTHEAD_JAM
1797
1798        elif e.text == "outputBinClosed":
1799            status_block['status-code'] = STATUS_PRINTER_CLEAR_OUTPUT_AREA
1800
1801        elif e.text == "outputBinOpened":
1802            status_block['status-code'] = STATUS_PRINTER_CLEAR_OUTPUT_AREA
1803
1804        elif e.text == "reseatDuplexer":
1805            status_block['status-code'] = STATUS_PRINTER_RESEAT_DUPLEXER
1806
1807        elif e.text == "unexpectedTypeInTray":
1808            status_block['status-code'] = STATUS_PRINTER_MEDIA_TYPE_MISMATCH
1809
1810        elif e.text == "manuallyFeed":
1811            status_block['status-code'] = STATUS_MANUALLY_FEED
1812
1813        else:
1814            status_block['status-code'] = STATUS_UNKNOWN_CODE
1815
1816    return status_block
1817
1818#IPP Status Code
1819IPP_PRINTER_STATE_IDLE = 0x03
1820IPP_PRINTER_STATE_PROCESSING = 0x04
1821IPP_PRINTER_STATE_STOPPED = 0x05
1822
1823marker_kind_xlate =    { 'ink' : AGENT_KIND_SUPPLY,
1824                         'rechargeableToner' : AGENT_KIND_TONER_CARTRIDGE,
1825                         'inkTank' : AGENT_KIND_SUPPLY,
1826                         'inkCartridge' : AGENT_KIND_SUPPLY,
1827                         'printhead' : AGENT_KIND_HEAD,
1828                         'toner' : AGENT_KIND_TONER_CARTRIDGE,
1829                         'tonerCartridge' : AGENT_KIND_TONER_CARTRIDGE,
1830                         'toner-cartridge' : AGENT_KIND_TONER_CARTRIDGE,
1831                         'maintenanceKit' : AGENT_KIND_MAINT_KIT,
1832                         'ink-cartridge' : AGENT_KIND_SUPPLY,
1833                       }
1834
1835marker_type_xlate = {'magenta ink' : AGENT_TYPE_MAGENTA,
1836                     'cyan ink' : AGENT_TYPE_CYAN,
1837                     'yellow ink' : AGENT_TYPE_YELLOW,
1838                     'black ink' : AGENT_TYPE_BLACK,
1839                     'Black Cartridge' : AGENT_TYPE_BLACK,
1840                     'Magenta Cartridge' : AGENT_TYPE_MAGENTA,
1841                     'Cyan Cartridge' : AGENT_TYPE_CYAN,
1842                     'Yellow Cartridge' : AGENT_TYPE_YELLOW,
1843                     'Maintenance Kit' : AGENT_TYPE_NONE,
1844                    }
1845
1846marker_leveltrigger_xlate = { 'ok' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
1847                              'low' : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
1848                              'out' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1849                              'empty' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1850                              'missing' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
1851                            }
1852
1853marker_state_xlate = { 'ok' : AGENT_HEALTH_OK,
1854                       'misinstalled' : AGENT_HEALTH_MISINSTALLED,
1855                       'missing' : AGENT_HEALTH_MISINSTALLED,
1856                     }
1857
1858printer_state_reasons_xlate = { 'none' : STATUS_PRINTER_IDLE,
1859                               'media-needed' : STATUS_PRINTER_OUT_OF_PAPER,
1860                               'media-jam' : STATUS_PRINTER_MEDIA_JAM,
1861                               'shutdown' : STATUS_PRINTER_TURNING_OFF,
1862                               'toner-low' : STATUS_PRINTER_LOW_TONER,
1863                               'toner-empty' : STATUS_PRINTER_EMPTY_TONER,
1864                               'cover-open' : STATUS_PRINTER_DOOR_OPEN,
1865                               'door-open' : STATUS_PRINTER_DOOR_OPEN,
1866                               'input-tray-missing' : STATUS_PRINTER_TRAY_2_3_DOOR_OPEN,
1867                               'media-low' : STATUS_PRINTER_OUT_OF_PAPER,
1868                               'media-empty' : STATUS_PRINTER_MEDIA_EMPTY_ERROR,
1869                               'output-tray-missing' : STATUS_PRINTER_TRAY_2_MISSING,
1870                               'output-area-almost-full' : STATUS_PRINTER_CLEAR_OUTPUT_AREA,
1871                               'output-area-full' : STATUS_PRINTER_CLEAR_OUTPUT_AREA,
1872                               'marker-supply-low' : STATUS_PRINTER_VERY_LOW_ON_INK,
1873                               'marker-supply-empty' : STATUS_PRINTER_VERY_LOW_ON_INK,
1874                               'paused' : STATUS_PRINTER_PAUSED,
1875                               'other' : STATUS_UNKNOWN_CODE,
1876                             }
1877
1878def StatusTypeIPPStatus(attrs):
1879
1880    status_block = {}
1881    if not attrs:
1882        return status_block
1883
1884    try:
1885        printer_state = attrs['printer-state'][0]
1886        printer_state_reasons = attrs['printer-state-reasons'][0]
1887
1888        if printer_state == IPP_PRINTER_STATE_IDLE:
1889            status_block['status-code'] = STATUS_PRINTER_IDLE
1890        elif printer_state == IPP_PRINTER_STATE_PROCESSING:
1891            status_block['status-code'] = STATUS_PRINTER_PRINTING
1892        else:
1893            printer_state_reasons = printer_state_reasons.replace("-error", "")
1894            printer_state_reasons = printer_state_reasons.replace("-warning", "")
1895            printer_state_reasons = printer_state_reasons.replace("-report", "")
1896            status_block['status-code'] = printer_state_reasons_xlate.get(printer_state_reasons, STATUS_PRINTER_IDLE)
1897
1898    except Exception as e:
1899        log.debug("Exception occured while updating printer-state [%s]" %e.args[0])
1900        status_block = {}
1901
1902    return status_block
1903
1904
1905def StatusTypeIPPAgents(attrs):
1906
1907    status_block = {}
1908    agents = []
1909
1910    if not attrs:
1911        return status_block
1912
1913    loopcntr = 0
1914    while(True ):
1915        try:
1916            if loopcntr >= len(attrs['marker-names']):
1917                break
1918
1919            if attrs['marker-types'][loopcntr] == 'maintenanceKit':
1920                loopcntr = loopcntr + 1
1921                continue
1922
1923            if attrs['marker-levels'][loopcntr] > attrs['marker-low-levels'][loopcntr] :
1924                state = 'ok'
1925            else:
1926                state = 'low'
1927
1928            #match the type if marker-type is something like 'Black Cartridge HP XXXX'
1929            mtype = [v for k,v in marker_type_xlate.items() if attrs['marker-names'][loopcntr].startswith(k)]
1930
1931            entry = { 'kind' : marker_kind_xlate.get(attrs['marker-types'][loopcntr], AGENT_KIND_NONE),
1932                      'type' : mtype[0] if len(mtype) > 0 else 0,
1933                      'health' : marker_state_xlate.get(state, AGENT_HEALTH_OK),
1934                      'level' : attrs['marker-levels'][loopcntr],
1935                      'level-trigger' : marker_leveltrigger_xlate.get(state, AGENT_LEVEL_TRIGGER_SUFFICIENT_0),
1936                      'agent-sku' : ''
1937                    }
1938
1939            log.debug("%s" % entry)
1940            agents.append(entry)
1941        except AttributeError:
1942            log.error("no value found for attribute")
1943            return []
1944
1945        loopcntr = loopcntr + 1
1946
1947    status_block['agents'] = agents
1948
1949    return status_block
1950
1951def StatusTypeIPP(device_uri):
1952    status_block = { 'revision' :    STATUS_REV_UNKNOWN,
1953                     'agents' :      [],
1954                     'top-door' :    TOP_DOOR_NOT_PRESENT,
1955                     'supply-door' : TOP_DOOR_NOT_PRESENT,
1956                     'duplexer' :    DUPLEXER_NOT_PRESENT,
1957                     'photo-tray' :  PHOTO_TRAY_NOT_PRESENT,
1958                     'in-tray1' :    IN_TRAY_NOT_PRESENT,
1959                     'in-tray2' :    IN_TRAY_NOT_PRESENT,
1960                     'media-path' :  MEDIA_PATH_NOT_PRESENT,
1961                     'status-code' : STATUS_PRINTER_IDLE,
1962                   }
1963
1964    status_attrs = cupsext.getStatusAttributes(device_uri)
1965
1966    if status_attrs:
1967        status_block.update(StatusTypeIPPAgents(status_attrs) )
1968        status_block.update(StatusTypeIPPStatus (status_attrs) )
1969
1970    return status_block
1971
1972
1973