#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) Copyright 2003-2015 HP Development Company, L.P.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Author: Don Welch, Narla Naga Samrat Chowdary, Yashwant Kumar Sahu
#



# Std Lib
import struct
import io
from .sixext import BytesIO, to_bytes_utf8, to_bytes_latin, to_string_latin, to_long

from .g import *
import xml.parsers.expat as expat
import re
  

try:
    from xml.etree import ElementTree
    etree_loaded = True
except ImportError:
    try:
        from elementtree.ElementTree import XML
        elementtree_loaded = True
    except ImportError:
        elementtree_loaded = False
    etree_loaded = False

# Local
from .g import *
from .codes import *
from . import pml, utils
cupsext = utils.import_ext('cupsext')
hpmudext = utils.import_ext('hpmudext')
"""
status dict structure:
    { 'revision' :     STATUS_REV_00 .. STATUS_REV_04,
      'agents' :       [ list of pens/agents/supplies (dicts) ],
      'top-door' :     TOP_DOOR_NOT_PRESENT | TOP_DOOR_CLOSED | TOP_DOOR_OPEN,
      'status-code' :  STATUS_...,
      'supply-door' :  SUPPLY_DOOR_NOT_PRESENT | SUPPLY_DOOR_CLOSED | SUPPLY_DOOR_OPEN.
      'duplexer' :     DUPLEXER_NOT_PRESENT | DUPLEXER_DOOR_CLOSED | DUPLEXER_DOOR_OPEN,
      'photo_tray' :   PHOTO_TRAY_NOT_PRESENT | PHOTO_TRAY_ENGAGED | PHOTO_TRAY_NOT_ENGAGED,
      'in-tray1' :     IN_TRAY_NOT_PRESENT | IN_TRAY_CLOSED | IN_TRAY_OPEN (| IN_TRAY_DEFAULT | IN_TRAY_LOCKED)*,
      'in-tray2' :     IN_TRAY_NOT_PRESENT | IN_TRAY_CLOSED | IN_TRAY_OPEN (| IN_TRAY_DEFAULT | IN_TRAY_LOCKED)*,
      'media-path' :   MEDIA_PATH_NOT_PRESENT | MEDIA_PATH_CUT_SHEET | MEDIA_PATH_BANNER | MEDIA_PATH_PHOTO,
    }

    * S:02 only

agent dict structure: (pens/supplies/agents/etc)
    { 'kind' :           AGENT_KIND_NONE ... AGENT_KIND_ADF_KIT,
      'type' :           TYPE_BLACK ... AGENT_TYPE_UNSPECIFIED,      # aka color
      'health' :         AGENT_HEALTH_OK ... AGENT_HEALTH_UNKNOWN,
      'level' :          0 ... 100,
      'level-trigger' :  AGENT_LEVEL_TRIGGER_SUFFICIENT_0 ... AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
    }
"""



# 'revision'
STATUS_REV_00 = 0x00
STATUS_REV_01 = 0x01
STATUS_REV_02 = 0x02
STATUS_REV_03 = 0x03
STATUS_REV_04 = 0x04
STATUS_REV_V  = 0xff
STATUS_REV_UNKNOWN = 0xfe

vstatus_xlate  = {'busy' : STATUS_PRINTER_BUSY,
                   'idle' : STATUS_PRINTER_IDLE,
                   'prnt' : STATUS_PRINTER_PRINTING,
                   'offf' : STATUS_PRINTER_TURNING_OFF,
                   'rprt' : STATUS_PRINTER_REPORT_PRINTING,
                   'cncl' : STATUS_PRINTER_CANCELING,
                   'iost' : STATUS_PRINTER_IO_STALL,
                   'dryw' : STATUS_PRINTER_DRY_WAIT_TIME,
                   'penc' : STATUS_PRINTER_PEN_CHANGE,
                   'oopa' : STATUS_PRINTER_OUT_OF_PAPER,
                   'bnej' : STATUS_PRINTER_BANNER_EJECT,
                   'bnmz' : STATUS_PRINTER_BANNER_MISMATCH,
                   'phmz' : STATUS_PRINTER_PHOTO_MISMATCH,
                   'dpmz' : STATUS_PRINTER_DUPLEX_MISMATCH,
                   'pajm' : STATUS_PRINTER_MEDIA_JAM,
                   'cars' : STATUS_PRINTER_CARRIAGE_STALL,
                   'paps' : STATUS_PRINTER_PAPER_STALL,
                   'penf' : STATUS_PRINTER_PEN_FAILURE,
                   'erro' : STATUS_PRINTER_HARD_ERROR,
                   'pwdn' : STATUS_PRINTER_POWER_DOWN,
                   'fpts' : STATUS_PRINTER_FRONT_PANEL_TEST,
                   'clno' : STATUS_PRINTER_CLEAN_OUT_TRAY_MISSING}

REVISION_2_TYPE_MAP = {0 : AGENT_TYPE_NONE,
                        1 : AGENT_TYPE_BLACK,
                        2 : AGENT_TYPE_CYAN,
                        3 : AGENT_TYPE_MAGENTA,
                        4 : AGENT_TYPE_YELLOW,
                        5 : AGENT_TYPE_BLACK,
                        6 : AGENT_TYPE_CYAN,
                        7 : AGENT_TYPE_MAGENTA,
                        8 : AGENT_TYPE_YELLOW,
                       }

STATUS_BLOCK_UNKNOWN = {'revision' : STATUS_REV_UNKNOWN,
                         'agents' : [],
                         'status-code' : STATUS_UNKNOWN,
                       }

NUM_PEN_POS = {STATUS_REV_00 : 16,
                STATUS_REV_01 : 16,
                STATUS_REV_02 : 16,
                STATUS_REV_03 : 18,
                STATUS_REV_04 : 22}

PEN_DATA_SIZE = {STATUS_REV_00 : 8,
                  STATUS_REV_01 : 8,
                  STATUS_REV_02 : 4,
                  STATUS_REV_03 : 8,
                  STATUS_REV_04 : 8}

STATUS_POS = {STATUS_REV_00 : 14,
               STATUS_REV_01 : 14,
               STATUS_REV_02 : 14,
               STATUS_REV_03 : 16,
               STATUS_REV_04 : 20}

def parseSStatus(s, z=''):
    revision = ''
    pens = []
    top_door = TOP_DOOR_NOT_PRESENT
    stat = STATUS_UNKNOWN
    supply_door = SUPPLY_DOOR_NOT_PRESENT
    duplexer = DUPLEXER_NOT_PRESENT
    photo_tray = PHOTO_TRAY_NOT_PRESENT
    in_tray1 = IN_TRAY_NOT_PRESENT
    in_tray2 = IN_TRAY_NOT_PRESENT
    media_path = MEDIA_PATH_NOT_PRESENT
    Z_SIZE = 6

    try:
        z1 = []
        if len(z) > 0:
            z_fields = z.split(',')

            for z_field in z_fields:

                if len(z_field) > 2 and z_field[:2] == '05':
                    z1s = z_field[2:]
                    z1 = [int(x, 16) for x in z1s]

        s1 = [int(x, 16) for x in s]

        revision = s1[1]

        assert STATUS_REV_00 <= revision <= STATUS_REV_04

        top_door = bool(s1[2] & to_long(0x8)) + s1[2] & to_long(0x1)
        supply_door = bool(s1[3] & to_long(0x8)) + s1[3] & to_long(0x1)
        duplexer = bool(s1[4] & to_long(0xc)) +  s1[4] & to_long(0x1)
        photo_tray = bool(s1[5] & 0x8) + s1[5] & 0x1

        if revision == STATUS_REV_02:
            in_tray1 = bool(s1[6] & to_long(0x8)) + s1[6] & to_long(0x1)
            in_tray2 = bool(s1[7] & to_long(0x8)) + s1[7] & to_long(0x1)
        else:
            in_tray1 = bool(s1[6] & to_long(0x8))
            in_tray2 = bool(s1[7] & to_long(0x8))

        media_path = bool(s1[8] & to_long(0x8)) + (s1[8] & to_long(0x1)) + ((bool(s1[18] & to_long(0x2)))<<1)
        status_pos = STATUS_POS[revision]
        status_byte = s1[status_pos]<<4
        if status_byte != 48:
            status_byte = (s1[status_pos]<<4) + s1[status_pos + 1]
        stat = status_byte + STATUS_PRINTER_BASE

        pen, c, d = {}, NUM_PEN_POS[revision]+1, 0
        num_pens = s1[NUM_PEN_POS[revision]]
        index = 0
        pen_data_size = PEN_DATA_SIZE[revision]

        log.debug("num_pens = %d" % num_pens)
        for p in range(num_pens):
            info = int(s[c : c + pen_data_size], 16)

            pen['index'] = index

            if pen_data_size == 4:
                pen['type'] = REVISION_2_TYPE_MAP.get(int((info & to_long(0xf000)) >> to_long(12)), 0)

                if index < (num_pens / 2):
                    pen['kind'] = AGENT_KIND_HEAD
                else:
                    pen['kind'] = AGENT_KIND_SUPPLY

                pen['level-trigger'] = int ((info & to_long(0x0e00)) >> to_long(9))
                pen['health'] = int((info & to_long(0x0180)) >> to_long(7))
                pen['level'] = int(info & to_long(0x007f))
                pen['id'] = 0x1f

            elif pen_data_size == 8:
                pen['kind'] = bool(info & to_long(0x80000000)) + ((bool(info & to_long(0x40000000)))<<to_long(1))
                pen['type'] = int((info & to_long(0x3f000000)) >> to_long(24))
                pen['id'] = int((info & 0xf80000) >> to_long(19))
                pen['level-trigger'] = int((info & to_long(0x70000)) >> to_long(16))
                pen['health'] = int((info & to_long(0xc000)) >> to_long(14))
                pen['level'] = int(info & to_long(0xff))

            else:
                log.error("Pen data size error")

            if len(z1) > 0:
                # TODO: Determine cause of IndexError for C6100 (defect #1111)
                try:
                    pen['dvc'] = int(z1s[d+1:d+5], 16)
                    pen['virgin'] = bool(z1[d+5] & to_long(0x8))
                    pen['hp-ink'] = bool(z1[d+5] & to_long(0x4))
                    pen['known'] = bool(z1[d+5] & to_long(0x2))
                    pen['ack'] = bool(z1[d+5] & to_long(0x1))
                except IndexError:
                    pen['dvc'] = 0
                    pen['virgin'] = 0
                    pen['hp-ink'] = 0
                    pen['known'] = 0
                    pen['ack'] = 0

            log.debug("pen %d %s" % (index, pen))

            index += 1
            pens.append(pen)
            pen = {}
            c += pen_data_size
            d += Z_SIZE

    except (IndexError, ValueError, TypeError) as e:
        log.warn("Status parsing error: %s" % str(e))

    return {'revision' :    revision,
             'agents' :      pens,
             'top-door' :    top_door,
             'status-code' : stat,
             'supply-door' : supply_door,
             'duplexer' :    duplexer,
             'photo-tray' :  photo_tray,
             'in-tray1' :    in_tray1,
             'in-tray2' :    in_tray2,
             'media-path' :  media_path,
           }



# $HB0$NC0,ff,DN,IDLE,CUT,K0,C0,DP,NR,KP092,CP041
#     0    1  2  3    4   5  6  7  8  9     10
def parseVStatus(s):
    pens, pen, c = [], {}, 0
    fields = s.split(',')
    log.debug(fields)
    f0 = fields[0]

    if len(f0) == 20:
        # TODO: $H00000000$M00000000 style (OJ Pro 1150/70)
        # Need spec
        pass
    elif len(f0) == 8:
        for p in f0:
            if c == 0:
                #assert p == '$'
                c += 1
            elif c == 1:
                if p in ('a', 'A'):
                    pen['type'], pen['kind'] = AGENT_TYPE_NONE, AGENT_KIND_NONE
                c += 1
            elif c == 2:
                pen['health'] = AGENT_HEALTH_OK
                pen['kind'] = AGENT_KIND_HEAD_AND_SUPPLY
                if   p in ('b', 'B'): pen['type'] = AGENT_TYPE_BLACK
                elif p in ('c', 'C'): pen['type'] = AGENT_TYPE_CMY
                elif p in ('d', 'D'): pen['type'] = AGENT_TYPE_KCM
                elif p in ('u', 'U'): pen['type'], pen['health'] = AGENT_TYPE_NONE, AGENT_HEALTH_MISINSTALLED
                c += 1
            elif c == 3:
                if p == '0': pen['state'] = 1
                else: pen['state'] = 0

                pen['level'] = 0
                i = 8

                while True:
                    try:
                        f = fields[i]
                    except IndexError:
                        break
                    else:
                        if f[:2] == 'KP' and pen['type'] == AGENT_TYPE_BLACK:
                            pen['level'] = int(f[2:])
                        elif f[:2] == 'CP' and pen['type'] == AGENT_TYPE_CMY:
                            pen['level'] = int(f[2:])
                    i += 1

                pens.append(pen)
                pen = {}
                c = 0
    else:
        pass

    try:
        fields[2]
    except IndexError:
        top_lid = 1 # something went wrong!
    else:
        if fields[2] == 'DN':
            top_lid = 1
        else:
            top_lid = 2

    try:
        stat = vstatus_xlate.get(fields[3].lower(), STATUS_PRINTER_IDLE)
    except IndexError:
        stat = STATUS_PRINTER_IDLE # something went wrong!

    return {'revision' :   STATUS_REV_V,
             'agents' :     pens,
             'top-door' :   top_lid,
             'status-code': stat,
             'supply-door': SUPPLY_DOOR_NOT_PRESENT,
             'duplexer' :   DUPLEXER_NOT_PRESENT,
             'photo-tray' : PHOTO_TRAY_NOT_PRESENT,
             'in-tray1' :   IN_TRAY_NOT_PRESENT,
             'in-tray2' :   IN_TRAY_NOT_PRESENT,
             'media-path' : MEDIA_PATH_CUT_SHEET, # ?
           }


def parseStatus(DeviceID):
    if 'VSTATUS' in DeviceID:
         return parseVStatus(DeviceID['VSTATUS'])
    elif 'S' in DeviceID:
        return parseSStatus(DeviceID['S'], DeviceID.get('Z', ''))
    else:
        return STATUS_BLOCK_UNKNOWN

def LaserJetDeviceStatusToPrinterStatus(device_status, printer_status, detected_error_state):
    stat = STATUS_PRINTER_IDLE

    if device_status in (pml.DEVICE_STATUS_WARNING, pml.DEVICE_STATUS_DOWN):

        if detected_error_state & pml.DETECTED_ERROR_STATE_LOW_PAPER_MASK and \
            not (detected_error_state & pml.DETECTED_ERROR_STATE_NO_PAPER_MASK):
            stat = STATUS_PRINTER_LOW_PAPER

        elif detected_error_state & pml.DETECTED_ERROR_STATE_NO_PAPER_MASK:
            stat = STATUS_PRINTER_OUT_OF_PAPER

        elif detected_error_state & pml.DETECTED_ERROR_STATE_DOOR_OPEN_MASK:
            stat = STATUS_PRINTER_DOOR_OPEN

        elif detected_error_state & pml.DETECTED_ERROR_STATE_JAMMED_MASK:
            stat = STATUS_PRINTER_MEDIA_JAM

        elif detected_error_state & pml.DETECTED_ERROR_STATE_OUT_CART_MASK:
            stat = STATUS_PRINTER_NO_TONER

        elif detected_error_state & pml.DETECTED_ERROR_STATE_LOW_CART_MASK:
            stat = STATUS_PRINTER_LOW_TONER

        elif detected_error_state == pml.DETECTED_ERROR_STATE_SERVICE_REQUEST_MASK:
            stat = STATUS_PRINTER_SERVICE_REQUEST

        elif detected_error_state & pml.DETECTED_ERROR_STATE_OFFLINE_MASK:
            stat = STATUS_PRINTER_OFFLINE

    else:

        if printer_status == pml.PRINTER_STATUS_IDLE:
            stat = STATUS_PRINTER_IDLE

        elif printer_status == pml.PRINTER_STATUS_PRINTING:
            stat = STATUS_PRINTER_PRINTING

        elif printer_status == pml.PRINTER_STATUS_WARMUP:
            stat = STATUS_PRINTER_WARMING_UP

    return stat

# Map from ISO 10175/10180 to HPLIP types
COLORANT_INDEX_TO_AGENT_TYPE_MAP = {
                                    'other' :   AGENT_TYPE_UNSPECIFIED,
                                    'unknown' : AGENT_TYPE_UNSPECIFIED,
                                    'blue' :    AGENT_TYPE_BLUE,
                                    'cyan' :    AGENT_TYPE_CYAN,
                                    'magenta':  AGENT_TYPE_MAGENTA,
                                    'yellow' :  AGENT_TYPE_YELLOW,
                                    'black' :   AGENT_TYPE_BLACK,
                                    'photoblack': AGENT_TYPE_PHOTO_BLACK,
                                    'matteblack' : AGENT_TYPE_MATTE_BLACK,
                                    'lightgray' : AGENT_TYPE_LG,
                                    'gray': AGENT_TYPE_G,
                                    'darkgray': AGENT_TYPE_DG,
                                    'lightcyan': AGENT_TYPE_LC,
                                    'lightmagenta': AGENT_TYPE_LM,
                                    'red' : AGENT_TYPE_RED,
                                   }

MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP = {
    pml.OID_MARKER_SUPPLIES_TYPE_OTHER :              AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_UNKNOWN :            AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_TONER :              AGENT_KIND_TONER_CARTRIDGE,
    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_TONER :        AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_INK :                AGENT_KIND_SUPPLY,
    pml.OID_MARKER_SUPPLIES_TYPE_INK_CART :           AGENT_KIND_HEAD_AND_SUPPLY,
    pml.OID_MARKER_SUPPLIES_TYPE_INK_RIBBON :         AGENT_KIND_HEAD_AND_SUPPLY,
    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_INK :          AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_OPC :                AGENT_KIND_DRUM_KIT,
    pml.OID_MARKER_SUPPLIES_TYPE_DEVELOPER :          AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OIL :          AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_SOLID_WAX :          AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_RIBBON_WAX :         AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_WASTE_WAX :          AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_FUSER :              AGENT_KIND_MAINT_KIT,
    pml.OID_MARKER_SUPPLIES_TYPE_CORONA_WIRE :        AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OIL_WICK :     AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_CLEANER_UNIT :       AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_CLEANING_PAD : AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_TRANSFER_UNIT :      AGENT_KIND_TRANSFER_KIT,
    pml.OID_MARKER_SUPPLIES_TYPE_TONER_CART :         AGENT_KIND_TONER_CARTRIDGE,
    pml.OID_MARKER_SUPPLIES_TYPE_FUSER_OILER :        AGENT_KIND_UNKNOWN,
    pml.OID_MARKER_SUPPLIES_TYPE_ADF_MAINT_KIT :      AGENT_KIND_ADF_KIT,
}


def StatusType3( dev, parsedID ): # LaserJet Status (PML/SNMP)
    try:
        dev.openPML()
        #result_code, on_off_line = dev.getPML( pml.OID_ON_OFF_LINE, pml.INT_SIZE_BYTE )
        #result_code, sleep_mode = dev.getPML( pml.OID_SLEEP_MODE, pml.INT_SIZE_BYTE )
        result_code, printer_status = dev.getPML( pml.OID_PRINTER_STATUS, pml.INT_SIZE_BYTE )
        result_code, device_status = dev.getPML( pml.OID_DEVICE_STATUS, pml.INT_SIZE_BYTE )
        result_code, cover_status = dev.getPML( pml.OID_COVER_STATUS, pml.INT_SIZE_BYTE )
        result_code, value = dev.getPML( pml.OID_DETECTED_ERROR_STATE )
    except Error:
       dev.closePML()

       return {'revision' :    STATUS_REV_UNKNOWN,
                 'agents' :      [],
                 'top-door' :    0,
                 'status-code' : STATUS_UNKNOWN,
                 'supply-door' : 0,
                 'duplexer' :    1,
                 'photo-tray' :  0,
                 'in-tray1' :    0,
                 'in-tray2' :    0,
                 'media-path' :  0,
               }

    try:
        detected_error_state = struct.unpack( 'B', to_bytes_latin(value[0]))[0]
    except (IndexError, TypeError):
        detected_error_state = pml.DETECTED_ERROR_STATE_OFFLINE_MASK

    agents, x = [], 1

    while True:
        log.debug( "%s Agent: %d %s" % ("*"*10, x, "*"*10))
        log.debug("OID_MARKER_SUPPLIES_TYPE_%d:" % x)
        oid = ( pml.OID_MARKER_SUPPLIES_TYPE_x % x, pml.OID_MARKER_SUPPLIES_TYPE_x_TYPE )
        result_code, value = dev.getPML( oid, pml.INT_SIZE_BYTE )

        if result_code != ERROR_SUCCESS or value is None:
            log.debug("End of supply information.")
            break

        for a in MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP:
            if value == a:
                agent_kind = MARKER_SUPPLES_TYPE_TO_AGENT_KIND_MAP[a]
                break
        else:
            agent_kind = AGENT_KIND_UNKNOWN

        # TODO: Deal with printers that return -1 and -2 for level and max (LJ3380)

        log.debug("OID_MARKER_SUPPLIES_LEVEL_%d:" % x)
        oid = ( pml.OID_MARKER_SUPPLIES_LEVEL_x % x, pml.OID_MARKER_SUPPLIES_LEVEL_x_TYPE )
        result_code, agent_level = dev.getPML( oid )

        if result_code != ERROR_SUCCESS:
            log.debug("Failed")
            break

        log.debug( 'agent%d-level: %d' % ( x, agent_level ) )
        log.debug("OID_MARKER_SUPPLIES_MAX_%d:" % x)
        oid = ( pml.OID_MARKER_SUPPLIES_MAX_x % x, pml.OID_MARKER_SUPPLIES_MAX_x_TYPE )
        result_code, agent_max = dev.getPML( oid )

        if agent_max == 0: agent_max = 1

        if result_code != ERROR_SUCCESS:
            log.debug("Failed")
            break

        log.debug( 'agent%d-max: %d' % ( x, agent_max ) )
        log.debug("OID_MARKER_SUPPLIES_COLORANT_INDEX_%d:" % x)
        oid = ( pml.OID_MARKER_SUPPLIES_COLORANT_INDEX_x % x, pml.OID_MARKER_SUPPLIES_COLORANT_INDEX_x_TYPE )
        result_code, colorant_index = dev.getPML( oid )

        if result_code != ERROR_SUCCESS: # 3080, 3055 will fail here
            log.debug("Failed")
            agent_type = AGENT_TYPE_BLACK
            #break
        else:
            log.debug("Colorant index: %d" % colorant_index)

            log.debug("OID_MARKER_COLORANT_VALUE_%d" % x)
            oid = ( pml.OID_MARKER_COLORANT_VALUE_x % colorant_index, pml.OID_MARKER_COLORANT_VALUE_x_TYPE )
            result_code, colorant_value = dev.getPML( oid )

            if result_code != ERROR_SUCCESS:
                log.debug("Failed. Defaulting to black.")
                agent_type = AGENT_TYPE_BLACK
            #else:
            if 1:
                if agent_kind in (AGENT_KIND_MAINT_KIT, AGENT_KIND_ADF_KIT,
                                  AGENT_KIND_DRUM_KIT, AGENT_KIND_TRANSFER_KIT):

                    agent_type = AGENT_TYPE_UNSPECIFIED

                else:
                    agent_type = AGENT_TYPE_BLACK

                    if result_code != ERROR_SUCCESS:
                        log.debug("OID_MARKER_SUPPLIES_DESCRIPTION_%d:" % x)
                        oid = (pml.OID_MARKER_SUPPLIES_DESCRIPTION_x % x, pml.OID_MARKER_SUPPLIES_DESCRIPTION_x_TYPE)
                        result_code, colorant_value = dev.getPML( oid )

                        if result_code != ERROR_SUCCESS:
                            log.debug("Failed")
                            break

                        if colorant_value is not None:
                            log.debug("colorant value: %s" % colorant_value)
                            colorant_value = colorant_value.lower().strip()

                            for c in COLORANT_INDEX_TO_AGENT_TYPE_MAP:
                                if colorant_value.find(c) >= 0:
                                    agent_type = COLORANT_INDEX_TO_AGENT_TYPE_MAP[c]
                                    break
                            else:
                                agent_type = AGENT_TYPE_BLACK

                    else: # SUCCESS
                        if colorant_value is not None:
                            log.debug("colorant value: %s" % colorant_value)
                            colorant_value = colorant_value.lower().strip()
                            agent_type = COLORANT_INDEX_TO_AGENT_TYPE_MAP.get( colorant_value, AGENT_TYPE_BLACK )

                        if agent_type == AGENT_TYPE_NONE:
                            if agent_kind == AGENT_KIND_TONER_CARTRIDGE:
                                agent_type = AGENT_TYPE_BLACK
                            else:
                                agent_type = AGENT_TYPE_UNSPECIFIED

        log.debug("OID_MARKER_STATUS_%d:" % x)
        oid = ( pml.OID_MARKER_STATUS_x % x, pml.OID_MARKER_STATUS_x_TYPE )
        result_code, agent_status = dev.getPML( oid )

        if result_code != ERROR_SUCCESS:
            log.debug("Failed")
            agent_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
            agent_health = AGENT_HEALTH_OK
        else:
            agent_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0

            if agent_status is None:
                agent_health = AGENT_HEALTH_OK

            elif agent_status == pml.OID_MARKER_STATUS_OK:
                agent_health = AGENT_HEALTH_OK

            elif agent_status == pml.OID_MARKER_STATUS_MISINSTALLED:
                agent_health = AGENT_HEALTH_MISINSTALLED

            elif agent_status in ( pml.OID_MARKER_STATUS_LOW_TONER_CONT,
                                   pml.OID_MARKER_STATUS_LOW_TONER_STOP ):

                agent_health = AGENT_HEALTH_OK
                agent_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW

            else:
                agent_health = AGENT_HEALTH_OK

        agent_level = int(float(agent_level)/agent_max * 100)


        log.debug("agent%d: kind=%d, type=%d, health=%d, level=%d, level-trigger=%d" % \
            (x, agent_kind, agent_type, agent_health, agent_level, agent_trigger))


        agents.append({'kind' : agent_kind,
                       'type' : agent_type,
                       'health' : agent_health,
                       'level' : agent_level,
                       'level-trigger' : agent_trigger,})

        x += 1

        if x > 20:
            break


    printer_status = printer_status or STATUS_PRINTER_IDLE
    log.debug("printer_status=%d" % printer_status)
    device_status = device_status or pml.DEVICE_STATUS_RUNNING
    log.debug("device_status=%d" % device_status)
    cover_status = cover_status or pml.COVER_STATUS_CLOSED
    log.debug("cover_status=%d" % cover_status)
    detected_error_state = detected_error_state or pml.DETECTED_ERROR_STATE_NO_ERROR
    log.debug("detected_error_state=%d (0x%x)" % (detected_error_state, detected_error_state))

    stat = LaserJetDeviceStatusToPrinterStatus(device_status, printer_status, detected_error_state)

    log.debug("Printer status=%d" % stat)

    if stat == STATUS_PRINTER_DOOR_OPEN:
        supply_door = 0
    else:
        supply_door = 1

    return {'revision' :    STATUS_REV_UNKNOWN,
             'agents' :      agents,
             'top-door' :    cover_status,
             'status-code' : stat,
             'supply-door' : supply_door,
             'duplexer' :    1,
             'photo-tray' :  0,
             'in-tray1' :    1,
             'in-tray2' :    1,
             'media-path' :  1,
           }

def setup_panel_translator():
    printables = list(
"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~""")

    map = {}
    for x in [chr(x) for x in range(0,256)]:
        if x in printables:
            map[x] = x
        else:
            map[x] = '\x20'

    map.update({'\x10' : '\xab',
                    '\x11' : '\xbb',
                    '\x12' : '\xa3',
                    '\x13' : '\xbb',
                    '\x80' : '\xab',
                    '\x81' : '\xbb',
                    '\x82' : '\x2a',
                    '\x83' : '\x2a',
                    '\x85' : '\x2a',
                    '\xa0' : '\xab',
                    '\x1f' : '\x3f',
                    '='    : '\x20',
                })

    frm, to = to_bytes_latin(''), to_bytes_latin('')
    map_keys = list(map.keys())
    map_keys.sort()
    for x in map_keys:
        frm = to_bytes_latin('').join([frm, to_bytes_latin(x)])
        to = to_bytes_latin('').join([to, to_bytes_latin(map[x])])

    global PANEL_TRANSLATOR_FUNC
    PANEL_TRANSLATOR_FUNC = utils.Translator(frm, to)

PANEL_TRANSLATOR_FUNC = None
setup_panel_translator()


def PanelCheck(dev):
    line1, line2 = to_bytes_utf8(''), ('')

    if dev.io_mode not in (IO_MODE_RAW, IO_MODE_UNI):

        try:
            dev.openPML()
        except Error:
            pass
        else:

            oids = [(pml.OID_HP_LINE1, pml.OID_HP_LINE2),
                     (pml.OID_SPM_LINE1, pml.OID_SPM_LINE2)]

            for oid1, oid2 in oids:
                result, line1 = dev.getPML(oid1)

                if result < pml.ERROR_MAX_OK:
                    line1 = PANEL_TRANSLATOR_FUNC(line1.encode('utf-8')).rstrip()

                    if to_bytes_utf8('\x0a') in line1:
                        line1, line2 = line1.split(to_bytes_utf8('\x0a'), 1)
                        break

                    result, line2 = dev.getPML(oid2)

                    if result < pml.ERROR_MAX_OK:
                        line2 = PANEL_TRANSLATOR_FUNC(line2.encode('utf-8')).rstrip()
                        break

    return bool(line1 or line2), line1 or to_bytes_utf8(''), line2 or to_bytes_utf8('')


BATTERY_HEALTH_MAP = {0 : AGENT_HEALTH_OK,
                       1 : AGENT_HEALTH_OVERTEMP,
                       2 : AGENT_HEALTH_CHARGING,
                       3 : AGENT_HEALTH_MISINSTALLED,
                       4 : AGENT_HEALTH_FAILED,
                      }


BATTERY_TRIGGER_MAP = {0 : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                        1 : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                        2 : AGENT_LEVEL_TRIGGER_PROBABLY_OUT,
                        3 : AGENT_LEVEL_TRIGGER_SUFFICIENT_4,
                        4 : AGENT_LEVEL_TRIGGER_SUFFICIENT_2,
                        5 : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                       }

BATTERY_PML_TRIGGER_MAP = {
        (100, 80)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
        (79,  60)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_1,
        (59,  40)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_2,
        (39,  30)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_3,
        (29,  20)  : AGENT_LEVEL_TRIGGER_SUFFICIENT_4,
        (19,  10)  : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
        (9,    5)  : AGENT_LEVEL_TRIGGER_PROBABLY_OUT,
        (4,   -1)  : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
        }


def BatteryCheck(dev, status_block, battery_check):
    try_dynamic_counters = False

    try:
        try:
            dev.openPML()
        except Error:
            if battery_check == STATUS_BATTERY_CHECK_STD:
                log.debug("PML channel open failed. Trying dynamic counters...")
                try_dynamic_counters = True
        else:
            if battery_check == STATUS_BATTERY_CHECK_PML:
                result, battery_level = dev.getPML(pml.OID_BATTERY_LEVEL_2)

                if result > pml.ERROR_MAX_OK:
                    status_block['agents'].append({
                        'kind'   : AGENT_KIND_INT_BATTERY,
                        'type'   : AGENT_TYPE_UNSPECIFIED,
                        'health' : AGENT_HEALTH_UNKNOWN,
                        'level'  : 0,
                        'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                        })
                    return

                else:
                    status_block['agents'].append({
                        'kind'   : AGENT_KIND_INT_BATTERY,
                        'type'   : AGENT_TYPE_UNSPECIFIED,
                        'health' : AGENT_HEALTH_OK,
                        'level'  : battery_level,
                        'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                        })
                    return

            else: # STATUS_BATTERY_CHECK_STD
                result, battery_level = dev.getPML(pml.OID_BATTERY_LEVEL)
                result, power_mode =  dev.getPML(pml.OID_POWER_MODE)

                if battery_level is not None and \
                    power_mode is not None:

                    if power_mode & pml.POWER_MODE_BATTERY_LEVEL_KNOWN and \
                        battery_level >= 0:

                        for x in BATTERY_PML_TRIGGER_MAP:
                            if x[0] >= battery_level > x[1]:
                                battery_trigger_level = BATTERY_PML_TRIGGER_MAP[x]
                                break

                        if power_mode & pml.POWER_MODE_CHARGING:
                            agent_health = AGENT_HEALTH_CHARGING

                        elif power_mode & pml.POWER_MODE_DISCHARGING:
                            agent_health = AGENT_HEALTH_DISCHARGING

                        else:
                            agent_health = AGENT_HEALTH_OK

                        status_block['agents'].append({
                            'kind'   : AGENT_KIND_INT_BATTERY,
                            'type'   : AGENT_TYPE_UNSPECIFIED,
                            'health' : agent_health,
                            'level'  : battery_level,
                            'level-trigger' : battery_trigger_level,
                            })
                        return

                    else:
                        status_block['agents'].append({
                            'kind'   : AGENT_KIND_INT_BATTERY,
                            'type'   : AGENT_TYPE_UNSPECIFIED,
                            'health' : AGENT_HEALTH_UNKNOWN,
                            'level'  : 0,
                            'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                            })
                        return

                else:
                    try_dynamic_counters = True

    finally:
        dev.closePML()


    if battery_check == STATUS_BATTERY_CHECK_STD and \
        try_dynamic_counters:

        try:
            try:
                battery_health = dev.getDynamicCounter(200)
                battery_trigger_level = dev.getDynamicCounter(201)
                battery_level = dev.getDynamicCounter(202)

                status_block['agents'].append({
                    'kind'   : AGENT_KIND_INT_BATTERY,
                    'type'   : AGENT_TYPE_UNSPECIFIED,
                    'health' : BATTERY_HEALTH_MAP[battery_health],
                    'level'  : battery_level,
                    'level-trigger' : BATTERY_TRIGGER_MAP[battery_trigger_level],
                    })
            except Error:
                status_block['agents'].append({
                    'kind'   : AGENT_KIND_INT_BATTERY,
                    'type'   : AGENT_TYPE_UNSPECIFIED,
                    'health' : AGENT_HEALTH_UNKNOWN,
                    'level'  : 0,
                    'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                    })
        finally:
            dev.closePrint()

    else:
        status_block['agents'].append({
            'kind'   : AGENT_KIND_INT_BATTERY,
            'type'   : AGENT_TYPE_UNSPECIFIED,
            'health' : AGENT_HEALTH_UNKNOWN,
            'level'  : 0,
            'level-trigger' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
            })



# this works for 2 pen products that allow 1 or 2 pens inserted
# from: k, kcm, cmy, ggk
def getPenConfiguration(s): # s=status dict from parsed device ID
    pens = [p['type'] for p in s['agents']]

    if utils.all(pens, lambda x : x==AGENT_TYPE_NONE):
        return AGENT_CONFIG_NONE

    if AGENT_TYPE_NONE in pens:

        if AGENT_TYPE_BLACK in pens:
            return AGENT_CONFIG_BLACK_ONLY

        elif AGENT_TYPE_CMY in pens:
            return AGENT_CONFIG_COLOR_ONLY

        elif AGENT_TYPE_KCM in pens:
            return AGENT_CONFIG_PHOTO_ONLY

        elif AGENT_TYPE_GGK in pens:
            return AGENT_CONFIG_GREY_ONLY

        else:
            return AGENT_CONFIG_INVALID

    else:
        if AGENT_TYPE_BLACK in pens and AGENT_TYPE_CMY in pens:
            return AGENT_CONFIG_COLOR_AND_BLACK

        elif AGENT_TYPE_CMY in pens and AGENT_TYPE_KCM in pens:
            return AGENT_CONFIG_COLOR_AND_PHOTO

        elif AGENT_TYPE_CMY in pens and AGENT_TYPE_GGK in pens:
            return AGENT_CONFIG_COLOR_AND_GREY

        else:
            return AGENT_CONFIG_INVALID


def getFaxStatus(dev):
    tx_active, rx_active = False, False

    if dev.io_mode not in (IO_MODE_UNI, IO_MODE_RAW):
        try:
            dev.openPML()

            result_code, tx_state = dev.getPML(pml.OID_FAXJOB_TX_STATUS)

            if result_code == ERROR_SUCCESS and tx_state:
                if tx_state not in (pml.FAXJOB_TX_STATUS_IDLE, pml.FAXJOB_TX_STATUS_DONE):
                    tx_active = True

            result_code, rx_state = dev.getPML(pml.OID_FAXJOB_RX_STATUS)

            if result_code == ERROR_SUCCESS and rx_state:
                if rx_state not in (pml.FAXJOB_RX_STATUS_IDLE, pml.FAXJOB_RX_STATUS_DONE):
                    rx_active = True

        finally:
            dev.closePML()

    return tx_active, rx_active


TYPE6_STATUS_CODE_MAP = {
     0    : STATUS_PRINTER_IDLE, #</DevStatusUnknown>
    -19928: STATUS_PRINTER_IDLE,
    -18995: STATUS_PRINTER_CANCELING,
    -17974: STATUS_PRINTER_WARMING_UP,
    -17973: STATUS_PRINTER_PEN_CLEANING, # sic
    -18993: STATUS_PRINTER_BUSY,
    -17949: STATUS_PRINTER_BUSY,
    -19720: STATUS_PRINTER_MANUAL_DUPLEX_BLOCK,
    -19678: STATUS_PRINTER_BUSY,
    -19695: STATUS_PRINTER_OUT_OF_PAPER,
    -17985: STATUS_PRINTER_MEDIA_JAM,
    -19731: STATUS_PRINTER_OUT_OF_PAPER,
    -18974: STATUS_PRINTER_BUSY, #?
    -19730: STATUS_PRINTER_OUT_OF_PAPER,
    -19729: STATUS_PRINTER_OUT_OF_PAPER,
    -19933: STATUS_PRINTER_HARD_ERROR, # out of memory
    -17984: STATUS_PRINTER_DOOR_OPEN,
    -19694: STATUS_PRINTER_DOOR_OPEN,
    -18992: STATUS_PRINTER_MANUAL_FEED_BLOCKED, # ?
    -19690: STATUS_PRINTER_MEDIA_JAM, # tray 1
    -19689: STATUS_PRINTER_MEDIA_JAM, # tray 2
    -19611: STATUS_PRINTER_MEDIA_JAM, # tray 3
    -19686: STATUS_PRINTER_MEDIA_JAM,
    -19688: STATUS_PRINTER_MEDIA_JAM, # paper path
    -19685: STATUS_PRINTER_MEDIA_JAM, # cart area
    -19684: STATUS_PRINTER_MEDIA_JAM, # output bin
    -18848: STATUS_PRINTER_MEDIA_JAM, # duplexer
    -18847: STATUS_PRINTER_MEDIA_JAM, # door open
    -18846: STATUS_PRINTER_MEDIA_JAM, # tray 2
    -19687: STATUS_PRINTER_MEDIA_JAM, # open door
    -17992: STATUS_PRINTER_MEDIA_JAM, # mispick
    -19700: STATUS_PRINTER_HARD_ERROR, # invalid driver
    -17996: STATUS_PRINTER_FUSER_ERROR, # fuser error
    -17983: STATUS_PRINTER_FUSER_ERROR,
    -17982: STATUS_PRINTER_FUSER_ERROR,
    -17981: STATUS_PRINTER_FUSER_ERROR,
    -17971: STATUS_PRINTER_FUSER_ERROR,
    -17995: STATUS_PRINTER_HARD_ERROR, # beam error
    -17994: STATUS_PRINTER_HARD_ERROR, # scanner error
    -17993: STATUS_PRINTER_HARD_ERROR, # fan error
    -18994: STATUS_PRINTER_HARD_ERROR,
    -17986: STATUS_PRINTER_HARD_ERROR,
    -19904: STATUS_PRINTER_HARD_ERROR,
    -19701: STATUS_PRINTER_NON_HP_INK, # [sic]
    -19613: STATUS_PRINTER_IDLE, # HP
    -19654: STATUS_PRINTER_NON_HP_INK, # [sic]
    -19682: STATUS_PRINTER_HARD_ERROR, # resinstall
    -19693: STATUS_PRINTER_IDLE, # ?? To Accept
    -19752: STATUS_PRINTER_LOW_TONER,
    -19723: STATUS_PRINTER_BUSY,
    -19703: STATUS_PRINTER_BUSY,
    -19739: STATUS_PRINTER_NO_TONER,
    -19927: STATUS_PRINTER_BUSY,
    -19932: STATUS_PRINTER_BUSY,
    -19931: STATUS_PRINTER_BUSY,
    -11989: STATUS_PRINTER_BUSY,
    -11995: STATUS_PRINTER_BUSY, # ADF loaded
    -19954: STATUS_PRINTER_CANCELING,
    -19955: STATUS_PRINTER_REPORT_PRINTING,
    -19956: STATUS_PRINTER_REPORT_PRINTING,
    -19934: STATUS_PRINTER_HARD_ERROR,
    -19930: STATUS_PRINTER_BUSY,
    -11990: STATUS_PRINTER_DOOR_OPEN,
    -11999: STATUS_PRINTER_MEDIA_JAM, # ADF
    -12000: STATUS_PRINTER_MEDIA_JAM, # ADF
    -11998: STATUS_PRINTER_MEDIA_JAM, # ADF
    -11986: STATUS_PRINTER_HARD_ERROR, # scanner
    -11994: STATUS_PRINTER_BUSY,
    -14967: STATUS_PRINTER_BUSY,
    -19912: STATUS_PRINTER_HARD_ERROR,
    -14962: STATUS_PRINTER_BUSY, # copy pending
    -14971: STATUS_PRINTER_BUSY, # copying
    -14973: STATUS_PRINTER_BUSY, # copying being canceled
    -14972: STATUS_PRINTER_BUSY, # copying canceled
    -14966: STATUS_PRINTER_DOOR_OPEN,
    -14974: STATUS_PRINTER_MEDIA_JAM,
    -14969: STATUS_PRINTER_HARD_ERROR,
    -14968: STATUS_PRINTER_HARD_ERROR,
    -12996: STATUS_PRINTER_BUSY, # scan
    -12994: STATUS_PRINTER_BUSY, # scan
    -12993: STATUS_PRINTER_BUSY, # scan
    -12991: STATUS_PRINTER_BUSY, # scan
    -12995: STATUS_PRINTER_BUSY, # scan
    -12997: STATUS_PRINTER_HARD_ERROR, # scan
    -12990: STATUS_PRINTER_BUSY,
    -12998: STATUS_PRINTER_BUSY,
    -13000: STATUS_PRINTER_DOOR_OPEN,
    -12999: STATUS_PRINTER_MEDIA_JAM,
    -13859: STATUS_PRINTER_BUSY,
    -13858: STATUS_PRINTER_BUSY, #</DevStatusDialingOut>
    -13868: STATUS_PRINTER_BUSY, #</DevStatusRedialPending>
    -13867: STATUS_PRINTER_BUSY, #</DevStatusFaxSendCanceled>
    -13857: STATUS_PRINTER_BUSY, #</DevStatusConnecting>
    -13856: STATUS_PRINTER_BUSY, #</DevStatusSendingPage>
    -13855: STATUS_PRINTER_BUSY, #</DevStatusOnePageSend>
    -13854: STATUS_PRINTER_BUSY, #</DevStatusMultiplePagesSent>
    -13853: STATUS_PRINTER_BUSY, #</DevStatusSenderCancelingFax>
    -13839: STATUS_PRINTER_BUSY, #</DevStatusIncomingCall>
    -13842: STATUS_PRINTER_BUSY, #</DevStatusBlockingFax>
    -13838: STATUS_PRINTER_BUSY, #</DevStatusReceivingFax>
    -13847: STATUS_PRINTER_BUSY, #</DevStatusSinglePageReceived>
    -13846: STATUS_PRINTER_BUSY, #</DevStatusDoublePagesReceived>
    -13845: STATUS_PRINTER_BUSY, #</DevStatusTriplePagesReceived>
    -13844: STATUS_PRINTER_BUSY, #</DevStatusPrintingFax>
    -13840: STATUS_PRINTER_BUSY, #</DevStatusCancelingFaxPrint>
    -13843: STATUS_PRINTER_BUSY, #</DevStatusFaxCancelingReceive>
    -13850: STATUS_PRINTER_BUSY, #</DevStatusFaxCanceledReceive>
    -13851: STATUS_PRINTER_BUSY, #</DevStatusFaxDelayedSendMemoryFull>
    -13836: STATUS_PRINTER_BUSY, #</DevStatusNoDialTone>
    -13864: STATUS_PRINTER_BUSY, #</DevStatusNoFaxAnswer>
    -13863: STATUS_PRINTER_BUSY, #</DevStatusFaxBusy>
    -13865: STATUS_PRINTER_BUSY, #</DevStatusNoDocumentSent>
    -13862: STATUS_PRINTER_BUSY, #</DevStatusFaxSendError>
    -13837: STATUS_PRINTER_BUSY, #</DevStatusT30Error>
    -13861: STATUS_PRINTER_BUSY, #</DevStatusFaxMemoryFullSend>
    -13866: STATUS_PRINTER_BUSY, #</DevStatusADFNotCleared>
    -13841: STATUS_PRINTER_BUSY, #</DevStatusNoFaxDetected>
    -13848: STATUS_PRINTER_BUSY, #</DevStatusFaxMemoryFullReceive>
    -13849: STATUS_PRINTER_BUSY, #</DevStatusFaxReceiveError>

}

def StatusType6(dev): #  LaserJet Status (XML)
    info_device_status = BytesIO()
    info_ssp = BytesIO()
    try:
        dev.getEWSUrl("/hp/device/info_device_status.xml", info_device_status)
        dev.getEWSUrl("/hp/device/info_ssp.xml", info_ssp)
    except:
        log.warn("Failed to get Device status information")
        pass

    info_device_status = info_device_status.getvalue()
    info_ssp = info_ssp.getvalue()

    device_status = {}
    ssp = {}

    if info_device_status:
        try:
            log.debug_block("info_device_status", to_string_latin(info_device_status))
            device_status = utils.XMLToDictParser().parseXML(info_device_status)
            log.debug(device_status)
        except expat.ExpatError:
            log.error("Device Status XML parse error")
            device_status = {}

    if info_ssp:
        try:
            log.debug_block("info_spp", to_string_latin(info_ssp))
            ssp = utils.XMLToDictParser().parseXML(info_ssp)
            log.debug(ssp)
        except expat.ExpatError:
            log.error("SSP XML parse error")
            ssp = {}

    status_code = device_status.get('devicestatuspage-devicestatus-statuslist-status-code-0', 0)

    if not status_code:
        status_code = ssp.get('devicestatuspage-devicestatus-statuslist-status-code-0', 0)

    black_supply_level = device_status.get('devicestatuspage-suppliesstatus-blacksupply-percentremaining', 0)
    black_supply_low = ssp.get('suppliesstatuspage-blacksupply-lowreached', 0)
    agents = []

    agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                     'type' : AGENT_TYPE_BLACK,
                     'health' : 0,
                     'level' : black_supply_level,
                     'level-trigger' : 0,
                  })

    if dev.tech_type == TECH_TYPE_COLOR_LASER:
        cyan_supply_level = device_status.get('devicestatuspage-suppliesstatus-cyansupply-percentremaining', 0)
        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_CYAN,
                         'health' : 0,
                         'level' : cyan_supply_level,
                         'level-trigger' : 0,
                      })

        magenta_supply_level = device_status.get('devicestatuspage-suppliesstatus-magentasupply-percentremaining', 0)
        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_MAGENTA,
                         'health' : 0,
                         'level' : magenta_supply_level,
                         'level-trigger' : 0,
                      })

        yellow_supply_level = device_status.get('devicestatuspage-suppliesstatus-yellowsupply-percentremaining', 0)
        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_YELLOW,
                         'health' : 0,
                         'level' : yellow_supply_level,
                         'level-trigger' : 0,
                      })

    return {'revision' :    STATUS_REV_UNKNOWN,
             'agents' :      agents,
             'top-door' :    0,
             'supply-door' : 0,
             'duplexer' :    1,
             'photo-tray' :  0,
             'in-tray1' :    1,
             'in-tray2' :    1,
             'media-path' :  1,
             'status-code' : TYPE6_STATUS_CODE_MAP.get(status_code, STATUS_PRINTER_IDLE),
           }

# PJL status codes
PJL_STATUS_MAP = {
    10001: STATUS_PRINTER_IDLE, # online
    10002: STATUS_PRINTER_OFFLINE, # offline
    10003: STATUS_PRINTER_WARMING_UP,
    10004: STATUS_PRINTER_BUSY, # self test
    10005: STATUS_PRINTER_BUSY, # reset
    10006: STATUS_PRINTER_LOW_TONER,
    10007: STATUS_PRINTER_CANCELING,
    10010: STATUS_PRINTER_SERVICE_REQUEST,
    10011: STATUS_PRINTER_OFFLINE,
    10013: STATUS_PRINTER_BUSY,
    10014: STATUS_PRINTER_REPORT_PRINTING,
    10015: STATUS_PRINTER_BUSY,
    10016: STATUS_PRINTER_BUSY,
    10017: STATUS_PRINTER_REPORT_PRINTING,
    10018: STATUS_PRINTER_BUSY,
    10019: STATUS_PRINTER_BUSY,
    10020: STATUS_PRINTER_BUSY,
    10021: STATUS_PRINTER_BUSY,
    10022: STATUS_PRINTER_REPORT_PRINTING,
    10023: STATUS_PRINTER_PRINTING,
    10024: STATUS_PRINTER_SERVICE_REQUEST,
    10025: STATUS_PRINTER_SERVICE_REQUEST,
    10026: STATUS_PRINTER_BUSY,
    10027: STATUS_PRINTER_MEDIA_JAM,
    10028: STATUS_PRINTER_REPORT_PRINTING,
    10029: STATUS_PRINTER_PRINTING,
    10030: STATUS_PRINTER_BUSY,
    10031: STATUS_PRINTER_BUSY,
    10032: STATUS_PRINTER_BUSY,
    10033: STATUS_PRINTER_SERVICE_REQUEST,
    10034: STATUS_PRINTER_CANCELING,
    10035: STATUS_PRINTER_PRINTING,
    10036: STATUS_PRINTER_WARMING_UP,
    10200: STATUS_PRINTER_LOW_BLACK_TONER,
    10201: STATUS_PRINTER_LOW_CYAN_TONER,
    10202: STATUS_PRINTER_LOW_MAGENTA_TONER,
    10203: STATUS_PRINTER_LOW_YELLOW_TONER,
    10204: STATUS_PRINTER_LOW_TONER, # order image drum
    10205: STATUS_PRINTER_LOW_BLACK_TONER, # order black drum
    10206: STATUS_PRINTER_LOW_CYAN_TONER, # order cyan drum
    10207: STATUS_PRINTER_LOW_MAGENTA_TONER, # order magenta drum
    10208: STATUS_PRINTER_LOW_YELLOW_TONER, # order yellow drum
    10209: STATUS_PRINTER_LOW_BLACK_TONER,
    10210: STATUS_PRINTER_LOW_CYAN_TONER,
    10211: STATUS_PRINTER_LOW_MAGENTA_TONER,
    10212: STATUS_PRINTER_LOW_YELLOW_TONER,
    10213: STATUS_PRINTER_SERVICE_REQUEST, # order transport kit
    10214: STATUS_PRINTER_SERVICE_REQUEST, # order cleaning kit
    10215: STATUS_PRINTER_SERVICE_REQUEST, # order transfer kit
    10216: STATUS_PRINTER_SERVICE_REQUEST, # order fuser kit
    10217: STATUS_PRINTER_SERVICE_REQUEST, # maintenance
    10218: STATUS_PRINTER_LOW_TONER,
    10300: STATUS_PRINTER_LOW_BLACK_TONER, # replace black toner
    10301: STATUS_PRINTER_LOW_CYAN_TONER, # replace cyan toner
    10302: STATUS_PRINTER_LOW_MAGENTA_TONER, # replace magenta toner
    10303: STATUS_PRINTER_LOW_YELLOW_TONER, # replace yellow toner
    10304: STATUS_PRINTER_SERVICE_REQUEST, # replace image drum
    10305: STATUS_PRINTER_SERVICE_REQUEST, # replace black drum
    10306: STATUS_PRINTER_SERVICE_REQUEST, # replace cyan drum
    10307: STATUS_PRINTER_SERVICE_REQUEST, # replace magenta drum
    10308: STATUS_PRINTER_SERVICE_REQUEST, # replace yellow drum
    10309: STATUS_PRINTER_SERVICE_REQUEST, # replace black cart
    10310: STATUS_PRINTER_SERVICE_REQUEST, # replace cyan cart
    10311: STATUS_PRINTER_SERVICE_REQUEST, # replace magenta cart
    10312: STATUS_PRINTER_SERVICE_REQUEST, # replace yellow cart
    10313: STATUS_PRINTER_SERVICE_REQUEST, # replace transport kit
    10314: STATUS_PRINTER_SERVICE_REQUEST, # replace cleaning kit
    10315: STATUS_PRINTER_SERVICE_REQUEST, # replace transfer kit
    10316: STATUS_PRINTER_SERVICE_REQUEST, # replace fuser kit
    10317: STATUS_PRINTER_SERVICE_REQUEST,
    10318: STATUS_PRINTER_SERVICE_REQUEST, # replace supplies
    10400: STATUS_PRINTER_NON_HP_INK, # [sic]
    10401: STATUS_PRINTER_IDLE,
    10402: STATUS_PRINTER_SERVICE_REQUEST,
    10403: STATUS_PRINTER_IDLE,
    # 11xyy - Background paper-loading
    # 12xyy - Background paper-tray status
    # 15xxy - Output-bin status
    # 20xxx - PJL parser errors
    # 25xxx - PJL parser warnings
    # 27xxx - PJL semantic errors
    # 30xxx - Auto continuable conditions
    30119: STATUS_PRINTER_MEDIA_JAM,
    # 32xxx - PJL file system errors
    # 35xxx - Potential operator intervention conditions
    # 40xxx - Operator intervention conditions
    40021: STATUS_PRINTER_DOOR_OPEN,
    40022: STATUS_PRINTER_MEDIA_JAM,
    40038: STATUS_PRINTER_LOW_TONER,
    40600: STATUS_PRINTER_NO_TONER,
    # 41xyy - Foreground paper-loading messages
    # 43xyy - Optional paper handling device messages
    # 44xyy - LJ 4xxx/5xxx paper jam messages
    # 50xxx - Hardware errors
    # 55xxx - Personality errors

}

MIN_PJL_ERROR_CODE = 10001
DEFAULT_PJL_ERROR_CODE = 10001

def MapPJLErrorCode(error_code, str_code=None):
    if error_code < MIN_PJL_ERROR_CODE:
        return STATUS_PRINTER_BUSY

    if str_code is None:
        str_code = str(error_code)

    if len(str_code) < 5:
        return STATUS_PRINTER_BUSY

    status_code = PJL_STATUS_MAP.get(error_code, None)

    if status_code is None:
        status_code = STATUS_PRINTER_BUSY

        if 10999 < error_code < 12000: # 11xyy - Background paper-loading
            # x = tray #
            # yy = media code
            tray = int(str_code[2])
            media = int(str_code[3:])
            log.debug("Background paper loading for tray #%d" % tray)
            log.debug("Media code = %d" % media)

        elif 11999 < error_code < 13000: # 12xyy - Background paper-tray status
            # x = tray #
            # yy = status code
            tray = int(str_code[2])
            status = int(str_code[3:])
            log.debug("Background paper tray status for tray #%d" % tray)
            log.debug("Status code = %d" % status)

        elif 14999 < error_code < 16000: # 15xxy - Output-bin status
            # xx = output bin
            # y = status code
            bin = int(str_code[2:4])
            status = int(str_code[4])
            log.debug("Output bin full for bin #%d" % bin)
            status_code = STATUS_PRINTER_OUTPUT_BIN_FULL

        elif 19999 < error_code < 28000: # 20xxx, 25xxx, 27xxx PJL errors
            status_code = STATUS_PRINTER_SERVICE_REQUEST

        elif 29999 < error_code < 31000: # 30xxx - Auto continuable conditions
            log.debug("Auto continuation condition #%d" % error_code)
            status_code = STATUS_PRINTER_BUSY

        elif 34999 < error_code < 36000: # 35xxx - Potential operator intervention conditions
            status_code = STATUS_PRINTER_SERVICE_REQUEST

        elif 39999 < error_code < 41000: # 40xxx - Operator intervention conditions
            status_code = STATUS_PRINTER_SERVICE_REQUEST

        elif 40999 < error_code < 42000: # 41xyy - Foreground paper-loading messages
            # x = tray
            # yy = media code
            tray = int(str_code[2])
            media = int(str_code[3:])
            log.debug("Foreground paper loading for tray #%d" % tray)
            log.debug("Media code = %d" % media)
            status_code = STATUS_PRINTER_OUT_OF_PAPER

        elif 41999 < error_code < 43000:
            status_code = STATUS_PRINTER_MEDIA_JAM

        elif 42999 < error_code < 44000: # 43xyy - Optional paper handling device messages
            status_code = STATUS_PRINTER_SERVICE_REQUEST

        elif 43999 < error_code < 45000: # 44xyy - LJ 4xxx/5xxx paper jam messages
            status_code = STATUS_PRINTER_MEDIA_JAM

        elif 49999 < error_code < 51000: # 50xxx - Hardware errors
            status_code = STATUS_PRINTER_HARD_ERROR

        elif 54999 < error_code < 56000 : # 55xxx - Personality errors
            status_code = STATUS_PRINTER_HARD_ERROR

    log.debug("Mapped PJL error code %d to status code %d" % (error_code, status_code))
    return status_code


pjl_code_pat = re.compile("""^CODE\s*=\s*(\d.*)$""", re.IGNORECASE)



def StatusType8(dev): #  LaserJet PJL (B&W only)
    try:
        # Will error if printer is busy printing...
        dev.openPrint()
    except Error as e:
        log.warn(e.msg)
        status_code = STATUS_PRINTER_BUSY
    else:
        try:
            try:
                dev.writePrint(to_bytes_utf8("\x1b%-12345X@PJL INFO STATUS \r\n\x1b%-12345X"))
                pjl_return = dev.readPrint(1024, timeout=5, allow_short_read=True)
                dev.close()

                log.debug_block("PJL return:", to_string_latin(pjl_return))

                str_code = '10001'

                for line in pjl_return.splitlines():
                    line = line.strip()
                    match = pjl_code_pat.match(line.decode('utf-8'))

                    if match is not None:
                        str_code = match.group(1)
                        break

                log.debug("Code = %s" % str_code)

                try:
                    error_code = int(str_code)
                except ValueError:
                    error_code = DEFAULT_PJL_ERROR_CODE

                log.debug("Error code = %d" % error_code)

                status_code = MapPJLErrorCode(error_code, str_code)
            except Error:
                status_code = STATUS_PRINTER_HARD_ERROR
        finally:
            try:
                dev.closePrint()
            except Error:
                pass

    agents = []

    # TODO: Only handles mono lasers...
    if status_code in (STATUS_PRINTER_LOW_TONER, STATUS_PRINTER_LOW_BLACK_TONER):
        health = AGENT_HEALTH_OK
        level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
        level = 0

    elif status_code == STATUS_PRINTER_NO_TONER:
        health = AGENT_HEALTH_MISINSTALLED
        level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW
        level = 0

    else:
        health = AGENT_HEALTH_OK
        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
        level = 100

    log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))

    agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                     'type' : AGENT_TYPE_BLACK,
                     'health' : health,
                     'level' : level,
                     'level-trigger' : level_trigger,
                  })

    if dev.tech_type == TECH_TYPE_COLOR_LASER:
        level = 100
        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
        if status_code == STATUS_PRINTER_LOW_CYAN_TONER:
            level = 0
            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW

        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))

        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_CYAN,
                         'health' : AGENT_HEALTH_OK,
                         'level' : level,
                         'level-trigger' : level_trigger,
                      })

        level = 100
        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
        if status_code == STATUS_PRINTER_LOW_MAGENTA_TONER:
            level = 0
            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW

        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))

        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_MAGENTA,
                         'health' : AGENT_HEALTH_OK,
                         'level' : level,
                         'level-trigger' : level_trigger,
                      })

        level = 100
        level_trigger = AGENT_LEVEL_TRIGGER_SUFFICIENT_0
        if status_code == STATUS_PRINTER_LOW_YELLOW_TONER:
            level = 0
            level_trigger = AGENT_LEVEL_TRIGGER_MAY_BE_LOW

        log.debug("Agent: health=%d, level=%d, trigger=%d" % (health, level, level_trigger))

        agents.append({  'kind' : AGENT_KIND_TONER_CARTRIDGE,
                         'type' : AGENT_TYPE_YELLOW,
                         'health' : AGENT_HEALTH_OK,
                         'level' : level,
                         'level-trigger' : level_trigger,
                      })

    if status_code == 40021:
        top_door = 0
    else:
        top_door = 1

    log.debug("Status code = %d" % status_code)

    return { 'revision' :    STATUS_REV_UNKNOWN,
             'agents' :      agents,
             'top-door' :    top_door,
             'supply-door' : top_door,
             'duplexer' :    0,
             'photo-tray' :  0,
             'in-tray1' :    1,
             'in-tray2' :    1,
             'media-path' :  1,
             'status-code' : status_code,
           }


element_type10_xlate = { 'ink' : AGENT_KIND_SUPPLY,
                         'rechargeableToner' : AGENT_KIND_TONER_CARTRIDGE,
                         'inkTank' : AGENT_KIND_SUPPLY,
                         'inkCartridge' : AGENT_KIND_HEAD_AND_SUPPLY,
                         'printhead' : AGENT_KIND_HEAD,
                         'toner' : AGENT_KIND_TONER_CARTRIDGE,
                         'tonerCartridge' : AGENT_KIND_TONER_CARTRIDGE,
                       }

pen_type10_xlate = { 'pK' : AGENT_TYPE_PHOTO_BLACK,
                     'CMY' : AGENT_TYPE_CMY,
                     'M' : AGENT_TYPE_MAGENTA,
                     'C' : AGENT_TYPE_CYAN,
                     'Y' : AGENT_TYPE_YELLOW,
                     'K' : AGENT_TYPE_BLACK,
                     'G' : AGENT_TYPE_G,
                     'mK' : AGENT_TYPE_MATTE_BLACK,
                   }

pen_level10_xlate = { 'ok' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                      'low' : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
                      'out' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                      'empty' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                      'missing' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                      'unknown' : AGENT_LEVEL_UNKNOWN,
                    }

pen_health10_xlate = { 'ok' : AGENT_HEALTH_OK,
                       'misinstalled' : AGENT_HEALTH_MISINSTALLED,
                       'missing' : AGENT_HEALTH_MISINSTALLED,
                       'unknown' : AGENT_HEALTH_UNKNOWN,
                     }


#ExtractXMLData will extract actual data from http response (Transfer-encoding:  chunked).
#For unchunked response it will not do anything.
def ExtractXMLData(data):
    if data[0:1] != b'<':
        size = -1
        temp = to_bytes_utf8("")
        while size:
            index = data.find(to_bytes_utf8('\r\n'))
            size = int(data[0:index+1], 16)
            temp = temp + data[index+2:index+2+size]
            data = data[index+2+size+2:len(data)]
        data = temp
    return data

def StatusType10FetchUrl(func, url, footer=""):
    data_fp = BytesIO()
    if footer:
        data = func(url, data_fp, footer)
    else:
        data = func(url, data_fp)
        if data:
            while data.find(to_bytes_utf8('\r\n\r\n')) != -1:
                data = data.split(to_bytes_utf8('\r\n\r\n'), 1)[1]
                if not data.startswith(to_bytes_utf8("HTTP")):
                    break

            if data:
                data = ExtractXMLData(data)
    return data

def StatusType10(func): # Low End Data Model
    status_block = { 'revision' :    STATUS_REV_UNKNOWN,
                     'agents' :      [],
                     'top-door' :    TOP_DOOR_NOT_PRESENT,
                     'supply-door' : TOP_DOOR_NOT_PRESENT,
                     'duplexer' :    DUPLEXER_NOT_PRESENT,
                     'photo-tray' :  PHOTO_TRAY_NOT_PRESENT,
                     'in-tray1' :    IN_TRAY_NOT_PRESENT,
                     'in-tray2' :    IN_TRAY_NOT_PRESENT,
                     'media-path' :  MEDIA_PATH_NOT_PRESENT,
                     'status-code' : STATUS_PRINTER_IDLE,
                   }

    if not etree_loaded and not elementtree_loaded:
        log.error("cannot get status for printer. please load ElementTree module")
        return status_block

    status_block = StatusType10Agents(func)

    temp_status_block = {}
    temp_status_block = StatusType10Media(func)
    status_block.update(temp_status_block)

    temp_status_block = {}
    temp_status_block = StatusType10Status(func)
    status_block.update(temp_status_block)

    return status_block


def StatusType10Agents(func): # Low End Data Model
    status_block = {}
    # Get the dynamic consumables configuration
    data = StatusType10FetchUrl(func, "/DevMgmt/ConsumableConfigDyn.xml")
    if not data:
        return status_block
    data = data.replace(to_bytes_utf8("ccdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("dd:"), to_bytes_utf8(""))

    # Parse the agent status XML
    agents = []
    try:
        if etree_loaded:
            tree = ElementTree.XML(data)
        if not etree_loaded and elementtree_loaded:
            tree = XML(data)
        elements = tree.findall("ConsumableInfo")
        for e in elements:
            health = AGENT_HEALTH_OK
            ink_level = 0
            agent_sku = ''
            try:
                type = e.find("ConsumableTypeEnum").text
                state = e.find("ConsumableLifeState/ConsumableState").text
                quantityState = e.find("ConsumableLifeState/MeasuredQuantityState").text

                # level
                if type == "ink" or type == "inkCartridge" or type == "toner" or type == "tonerCartridge" or type == "rechargeableToner" or type == "inkTank":
                    ink_type = e.find("ConsumableLabelCode").text
                    if state != "missing":
                        try:
                           ink_level = int(e.find("ConsumablePercentageLevelRemaining").text)
                           if ink_level == 0 and quantityState == 'unknown':
                                state = "unknown"
                           elif ink_level == 0:
                               state = "empty"
                           elif ink_level <=10:
                               state = "low"

                           agent_sku = 'Unknown' #Initialize to unknown. IN some old devices, ConsumableSelectibilityNumber is not returned by device.
                        except:
                           ink_level = 0
                elif type == "printhead" or type == 'imageDrum':
                     continue; #No need of adding this agent.
                else:
                    ink_type = ''
                    if state == "ok":
                        ink_level = 100

                try:
                    agent_sku = e.find("ProductNumber").text
                except:
                    try :
                        agent_sku = e.find("ConsumableSelectibilityNumber").text
                    except :
                        pass

                log.debug("type '%s' state '%s' ink_type '%s' ink_level %d agent_sku = %s" % (type, state, ink_type, ink_level,agent_sku))

                entry = { 'kind' : element_type10_xlate.get(type, AGENT_KIND_NONE),
                          'type' : pen_type10_xlate.get(ink_type, AGENT_TYPE_NONE),
                          'health' : pen_health10_xlate.get(state, AGENT_HEALTH_OK),
                          'level' : int(ink_level),
                          'level-trigger' : pen_level10_xlate.get(state, AGENT_LEVEL_TRIGGER_SUFFICIENT_0),
                          'agent-sku' : agent_sku
                        }

                log.debug("%s" % entry)
                agents.append(entry)
            except AttributeError:
                log.debug("no value found for attribute")
    except (expat.ExpatError, UnboundLocalError):
        agents = []
    status_block['agents'] = agents

    return status_block

def StatusType10Media(func): # Low End Data Model
    status_block = {}
    # Get the media handling configuration
    data = StatusType10FetchUrl(func, "/DevMgmt/MediaHandlingDyn.xml")
    if not data:
        return status_block
    data = data.replace(to_bytes_utf8("mhdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("dd:"), to_bytes_utf8(""))

    # Parse the media handling XML
    try:
        if etree_loaded:
            tree = ElementTree.XML(data)
        if not etree_loaded and elementtree_loaded:
            tree = XML(data)
        elements = tree.findall("InputTray")
    except (expat.ExpatError, UnboundLocalError):
        elements = []
    for e in elements:
        bin_name = e.find("InputBin").text
        if bin_name == "Tray1":
            status_block['in-tray1'] = IN_TRAY_PRESENT
        elif bin_name == "Tray2":
            status_block['in-tray2'] = IN_TRAY_PRESENT
        elif bin_name == "PhotoTray":
            status_block['photo-tray'] = PHOTO_TRAY_ENGAGED

    try:
        elements = tree.findall("Accessories/MediaHandlingDeviceFunctionType")
    except UnboundLocalError:
        elements = []
    for e in elements:
        if e.text == "autoDuplexor":
            status_block['duplexer'] = DUPLEXER_DOOR_CLOSED

    return status_block

def StatusType10Status(func): # Low End Data Model
    status_block = {}
    # Get the product status
    data = StatusType10FetchUrl(func, "/DevMgmt/ProductStatusDyn.xml")
    if not data:
        return status_block
    data = data.replace(to_bytes_utf8("psdyn:"), to_bytes_utf8("")).replace(to_bytes_utf8("locid:"), to_bytes_utf8(""))
    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(""))

    # Parse the product status XML
    try:
        if etree_loaded:
            tree = ElementTree.XML(data)
        if not etree_loaded and elementtree_loaded:
            tree = XML(data)
        elements = tree.findall("Status/StatusCategory")
    except (expat.ExpatError, UnboundLocalError):
        elements = []

    for e in elements:

        if e.text == "processing":
            status_block['status-code'] = STATUS_PRINTER_PRINTING
        elif e.text == "ready":
            status_block['status-code'] = STATUS_PRINTER_IDLE
        elif e.text == "closeDoorOrCover":
            status_block['status-code'] = STATUS_PRINTER_DOOR_OPEN
        elif e.text == "shuttingDown":
            status_block['status-code'] = STATUS_PRINTER_TURNING_OFF
        elif e.text == "cancelJob":
            status_block['status-code'] = STATUS_PRINTER_CANCELING
        elif e.text == "trayEmptyOrOpen":
            status_block['status-code'] = STATUS_PRINTER_OUT_OF_PAPER
        elif e.text == "jamInPrinter":
            status_block['status-code'] = STATUS_PRINTER_MEDIA_JAM
        elif e.text == "hardError":
            status_block['status-code'] = STATUS_PRINTER_HARD_ERROR
        elif e.text == "outputBinFull":
            status_block['status-code'] = STATUS_PRINTER_OUTPUT_BIN_FULL
        elif e.text == "unexpectedSizeInTray" or e.text == "sizeMismatchInTray":
            status_block['status-code'] = STATUS_PRINTER_MEDIA_SIZE_MISMATCH
        elif e.text == "insertOrCloseTray2":
            status_block['status-code'] = STATUS_PRINTER_TRAY_2_MISSING
        elif e.text == "scannerError":
            status_block['status-code'] = EVENT_SCANNER_FAIL
        elif e.text == "scanProcessing":
            status_block['status-code'] = EVENT_START_SCAN_JOB
        elif e.text == "scannerAdfLoaded":
            status_block['status-code'] = EVENT_SCAN_ADF_LOADED
        elif e.text == "scanToDestinationNotSet":
            status_block['status-code'] = EVENT_SCAN_TO_DESTINATION_NOTSET
        elif e.text == "scanWaitingForPC":
            status_block['status-code'] = EVENT_SCAN_WAITING_FOR_PC
        elif e.text == "scannerAdfJam":
            status_block['status-code'] = EVENT_SCAN_ADF_JAM
        elif e.text == "scannerAdfDoorOpen":
            status_block['status-code'] = EVENT_SCAN_ADF_DOOR_OPEN
        elif e.text == "faxProcessing":
            status_block['status-code'] = EVENT_START_FAX_JOB
        elif e.text == "faxSending":
            status_block['status-code'] = STATUS_FAX_TX_ACTIVE
        elif e.text == "faxReceiving":
            status_block['status-code'] = STATUS_FAX_RX_ACTIVE
        elif e.text == "faxDialing":
            status_block['status-code'] = EVENT_FAX_DIALING
        elif e.text == "faxConnecting":
            status_block['status-code'] = EVENT_FAX_CONNECTING
        elif e.text == "faxSendError":
            status_block['status-code'] = EVENT_FAX_SEND_ERROR
        elif e.text == "faxErrorStorageFull":
            status_block['status-code'] = EVENT_FAX_ERROR_STORAGE_FULL
        elif e.text == "faxReceiveError":
            status_block['status-code'] = EVENT_FAX_RECV_ERROR
        elif e.text == "faxBlocking":
            status_block['status-code'] = EVENT_FAX_BLOCKING
        elif e.text == "inPowerSave":
            status_block['status-code'] = STATUS_PRINTER_POWER_SAVE
        elif e.text == "incorrectCartridge":
            status_block['status-code'] = STATUS_PRINTER_CARTRIDGE_WRONG
        elif e.text == "cartridgeMissing":
            status_block['status-code'] = STATUS_PRINTER_CARTRIDGE_MISSING
        elif e.text == "missingPrintHead":
            status_block['status-code'] = STATUS_PRINTER_PRINTHEAD_MISSING


        #Alert messages for Pentane products RQ 8888
        elif e.text == "scannerADFMispick":
            status_block['status-code'] = STATUS_SCANNER_ADF_MISPICK

        elif e.text == "mediaTooShortToAutoDuplex":
            status_block['status-code'] = STATUS_PRINTER_PAPER_TOO_SHORT_TO_AUTODUPLEX

        elif e.text == "insertOrCloseTray":
            status_block['status-code'] = STATUS_PRINTER_TRAY_2_3_DOOR_OPEN

        elif e.text == "inkTooLowToPrime":
            status_block['status-code'] = STATUS_PRINTER_INK_TOO_LOW_TO_PRIME

        elif e.text == "cartridgeVeryLow":
            status_block['status-code'] = STATUS_PRINTER_VERY_LOW_ON_INK

        elif e.text == "wasteMarkerCollectorAlmostFull":
            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_ALMOST_FULL

        elif e.text == "wasteMarkerCollectorFull":
            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_FULL

        elif e.text == "wasteMarkerCollectorFullPrompt":
            status_block['status-code'] = STATUS_PRINTER_SERVICE_INK_CONTAINER_FULL_PROMPT

        elif e.text == "missingDuplexer":
            status_block['status-code'] = STATUS_PRINTER_DUPLEX_MODULE_MISSING

        elif e.text == "printBarStall":
            status_block['status-code'] = STATUS_PRINTER_PRINTHEAD_JAM

        elif e.text == "outputBinClosed":
            status_block['status-code'] = STATUS_PRINTER_CLEAR_OUTPUT_AREA

        elif e.text == "outputBinOpened":
            status_block['status-code'] = STATUS_PRINTER_CLEAR_OUTPUT_AREA

        elif e.text == "reseatDuplexer":
            status_block['status-code'] = STATUS_PRINTER_RESEAT_DUPLEXER

        elif e.text == "unexpectedTypeInTray":
            status_block['status-code'] = STATUS_PRINTER_MEDIA_TYPE_MISMATCH

        elif e.text == "manuallyFeed":
            status_block['status-code'] = STATUS_MANUALLY_FEED

        else:
            status_block['status-code'] = STATUS_UNKNOWN_CODE

    return status_block

#IPP Status Code
IPP_PRINTER_STATE_IDLE = 0x03
IPP_PRINTER_STATE_PROCESSING = 0x04
IPP_PRINTER_STATE_STOPPED = 0x05

marker_kind_xlate =    { 'ink' : AGENT_KIND_SUPPLY,
                         'rechargeableToner' : AGENT_KIND_TONER_CARTRIDGE,
                         'inkTank' : AGENT_KIND_SUPPLY,
                         'inkCartridge' : AGENT_KIND_SUPPLY,
                         'printhead' : AGENT_KIND_HEAD,
                         'toner' : AGENT_KIND_TONER_CARTRIDGE,
                         'tonerCartridge' : AGENT_KIND_TONER_CARTRIDGE,
                         'toner-cartridge' : AGENT_KIND_TONER_CARTRIDGE,
                         'maintenanceKit' : AGENT_KIND_MAINT_KIT,
                         'ink-cartridge' : AGENT_KIND_SUPPLY,
                       }

marker_type_xlate = {'magenta ink' : AGENT_TYPE_MAGENTA,
                     'cyan ink' : AGENT_TYPE_CYAN,
                     'yellow ink' : AGENT_TYPE_YELLOW,
                     'black ink' : AGENT_TYPE_BLACK,
                     'Black Cartridge' : AGENT_TYPE_BLACK,
                     'Magenta Cartridge' : AGENT_TYPE_MAGENTA,
                     'Cyan Cartridge' : AGENT_TYPE_CYAN,
                     'Yellow Cartridge' : AGENT_TYPE_YELLOW,
                     'Maintenance Kit' : AGENT_TYPE_NONE,
                    }

marker_leveltrigger_xlate = { 'ok' : AGENT_LEVEL_TRIGGER_SUFFICIENT_0,
                              'low' : AGENT_LEVEL_TRIGGER_MAY_BE_LOW,
                              'out' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                              'empty' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                              'missing' : AGENT_LEVEL_TRIGGER_ALMOST_DEFINITELY_OUT,
                            }

marker_state_xlate = { 'ok' : AGENT_HEALTH_OK,
                       'misinstalled' : AGENT_HEALTH_MISINSTALLED,
                       'missing' : AGENT_HEALTH_MISINSTALLED,
                     }

printer_state_reasons_xlate = { 'none' : STATUS_PRINTER_IDLE,
                               'media-needed' : STATUS_PRINTER_OUT_OF_PAPER,
                               'media-jam' : STATUS_PRINTER_MEDIA_JAM,
                               'shutdown' : STATUS_PRINTER_TURNING_OFF,
                               'toner-low' : STATUS_PRINTER_LOW_TONER,
                               'toner-empty' : STATUS_PRINTER_EMPTY_TONER,
                               'cover-open' : STATUS_PRINTER_DOOR_OPEN,
                               'door-open' : STATUS_PRINTER_DOOR_OPEN,
                               'input-tray-missing' : STATUS_PRINTER_TRAY_2_3_DOOR_OPEN,
                               'media-low' : STATUS_PRINTER_OUT_OF_PAPER,
                               'media-empty' : STATUS_PRINTER_MEDIA_EMPTY_ERROR,
                               'output-tray-missing' : STATUS_PRINTER_TRAY_2_MISSING,
                               'output-area-almost-full' : STATUS_PRINTER_CLEAR_OUTPUT_AREA,
                               'output-area-full' : STATUS_PRINTER_CLEAR_OUTPUT_AREA,
                               'marker-supply-low' : STATUS_PRINTER_VERY_LOW_ON_INK,
                               'marker-supply-empty' : STATUS_PRINTER_VERY_LOW_ON_INK,
                               'paused' : STATUS_PRINTER_PAUSED,
                               'other' : STATUS_UNKNOWN_CODE,
                             }

def StatusTypeIPPStatus(attrs):

    status_block = {}
    if not attrs:
        return status_block

    try:
        printer_state = attrs['printer-state'][0]
        printer_state_reasons = attrs['printer-state-reasons'][0]

        if printer_state == IPP_PRINTER_STATE_IDLE:
            status_block['status-code'] = STATUS_PRINTER_IDLE
        elif printer_state == IPP_PRINTER_STATE_PROCESSING:
            status_block['status-code'] = STATUS_PRINTER_PRINTING
        else:
            printer_state_reasons = printer_state_reasons.replace("-error", "")
            printer_state_reasons = printer_state_reasons.replace("-warning", "")
            printer_state_reasons = printer_state_reasons.replace("-report", "")
            status_block['status-code'] = printer_state_reasons_xlate.get(printer_state_reasons, STATUS_PRINTER_IDLE)

    except Exception as e:
        log.debug("Exception occured while updating printer-state [%s]" %e.args[0])
        status_block = {}

    return status_block


def StatusTypeIPPAgents(attrs):

    status_block = {}
    agents = []

    if not attrs:
        return status_block

    loopcntr = 0
    while(True ):
        try:
            if loopcntr >= len(attrs['marker-names']):
                break

            if attrs['marker-types'][loopcntr] == 'maintenanceKit':
                loopcntr = loopcntr + 1
                continue

            if attrs['marker-levels'][loopcntr] > attrs['marker-low-levels'][loopcntr] :
                state = 'ok'
            else:
                state = 'low'

            #match the type if marker-type is something like 'Black Cartridge HP XXXX'
            mtype = [v for k,v in marker_type_xlate.items() if attrs['marker-names'][loopcntr].startswith(k)]

            entry = { 'kind' : marker_kind_xlate.get(attrs['marker-types'][loopcntr], AGENT_KIND_NONE),
                      'type' : mtype[0] if len(mtype) > 0 else 0,
                      'health' : marker_state_xlate.get(state, AGENT_HEALTH_OK),
                      'level' : attrs['marker-levels'][loopcntr],
                      'level-trigger' : marker_leveltrigger_xlate.get(state, AGENT_LEVEL_TRIGGER_SUFFICIENT_0),
                      'agent-sku' : ''
                    }

            log.debug("%s" % entry)
            agents.append(entry)
        except AttributeError:
            log.error("no value found for attribute")
            return []

        loopcntr = loopcntr + 1

    status_block['agents'] = agents

    return status_block

def StatusTypeIPP(device_uri,printer_name):
    status_block = { 'revision' :    STATUS_REV_UNKNOWN,
                     'agents' :      [],
                     'top-door' :    TOP_DOOR_NOT_PRESENT,
                     'supply-door' : TOP_DOOR_NOT_PRESENT,
                     'duplexer' :    DUPLEXER_NOT_PRESENT,
                     'photo-tray' :  PHOTO_TRAY_NOT_PRESENT,
                     'in-tray1' :    IN_TRAY_NOT_PRESENT,
                     'in-tray2' :    IN_TRAY_NOT_PRESENT,
                     'media-path' :  MEDIA_PATH_NOT_PRESENT,
                     'status-code' : STATUS_PRINTER_IDLE,
                   }

    status_attrs = cupsext.getStatusAttributes(device_uri,printer_name)

    if status_attrs:
        status_block.update(StatusTypeIPPAgents(status_attrs) )
        status_block.update(StatusTypeIPPStatus (status_attrs) )

    return status_block


