On branch master
Initial commit
	new file:   GPL-2.0.txt
	new file:   PyDPF.py
	new file:   README
	new file:   SyncThreads.py
	new file:   config.py
	new file:   dpflib.py
	new file:   dpfrender.py
	new file:   evdev.py
	new file:   gfx/AluminArt.png
	new file:   gfx/AluminArtC.png
	new file:   gfx/README_GFX
	new file:   gfx/burlap.png
	new file:   gfx/demo.png
	new file:   gfx/desert.png
	new file:   gfx/emptycase.png
	new file:   gfx/lether.png
	new file:   input.py
	new file:   themes.py
Redisign of display , virtual display added, new QT4display

	new file:   display/__init__.py
	new file:   display/__init__.pyc
	renamed:    dpflib.py -> display/dpflib.py
	new file:   display/dpflib.pyc
	new file:   display/pearldisplay.py
	new file:   display/qtdisplay.py
	new file:   display/qtdisplay.pyc
	new file:   display/vdisplay.pyc

	modified:   PyDPF.py
	modified:   config.py
	modified:   dpfrender.py
	new file:   mailreader.py
	deleted:    display/__init__.pyc
	deleted:    display/dpflib.pyc
	deleted:    display/qtdisplay.pyc
	deleted:    display/vdisplay.pyc
display packages adaption
PearlDPF maybe still broken !!

	modified:   PyDPF.py
	modified:   config.py
	modified:   dpfrender.py
bugfixes for qtdisplay and PearlDisplay

	new file:   display/vdisplay.py

	modified:   PyDPF.py
	modified:   config.py
	modified:   display/__init__.py
	modified:   display/dpflib.py
	modified:   display/pearldisplay.py
	modified:   display/qtdisplay.py
	modified:   dpfrender.py
	modified:   gfx/burlap.png
	modified:   mailreader.py
	modified:   themes.py
Update

	modified:   PyDPF.py
	modified:   config.py
	modified:   display/__init__.py
	modified:   display/dpflib.py
	modified:   display/pearldisplay.py
	modified:   display/qtdisplay.py
	modified:   dpfrender.py
	modified:   gfx/burlap.png
	modified:   mailreader.py
	modified:   themes.py
bugfix of mailreader

	modified:   PyDPF.py
	modified:   config.py
	modified:   mailreader.py
QT display & email update
	modified:   PyDPF.py
	modified:   config.py
	modified:   display/qtdisplay.py
	modified:   dpfrender.py
	new file:   gfx/black.png
	new file:   gfx/icons/mail-unread.png
	modified:   mailreader.py
	modified:   themes.py
weather plugin preparation

	modified:   config.py
	new file:   utils/__init__.py
	new file:   utils/pywapi.py
	new file:   utils/weather.py
weather interface completed (only current weather)

	new file:   gfx/icons/weather-clear.png
	new file:   gfx/icons/weather-clouds.png
	new file:   gfx/icons/weather-few-clouds.png
	new file:   gfx/icons/weather-freezing-rain.png
	new file:   gfx/icons/weather-hail.png
	new file:   gfx/icons/weather-many-clouds.png
	new file:   gfx/icons/weather-mist.png
	new file:   gfx/icons/weather-showers-scattered.png
	new file:   gfx/icons/weather-showers.png
	new file:   gfx/icons/weather-snow-rain.png
	new file:   gfx/icons/weather-snow-scattered.png
	new file:   gfx/icons/weather-snow.png
	new file:   gfx/icons/weather-storm.png
	modified:   utils/weather.py
weather interface update
	modified:   utils/pywapi.py
	modified:   utils/weather.py
mapped state added
weather integration

	new file:   HISTORY
	modified:   PyDPF.py
	modified:   README
	modified:   config.py
	modified:   display/qtdisplay.py
	modified:   dpfrender.py
	modified:   evdev.py
	modified:   themes.py
	renamed:    mailreader.py -> utils/mailreader.py
	modified:   utils/pywapi.py
	modified:   utils/weather.py
weather integration
	modified:   config.py
	modified:   dpfrender.py
	modified:   themes.py
bugfixes & optimsed

	modified:   HISTORY
	modified:   PyDPF.py
	modified:   README
	modified:   SyncThreads.py
	modified:   config.py
	modified:   display/qtdisplay.py
	modified:   dpfrender.py
	modified:   gfx/README_GFX
	modified:   themes.py
	renamed:    evdev.py -> utils/evdev.py
weather forecast interface
	modified:   utils/pywapi.py
	modified:   utils/weather.py
Preparation for fast c based dpflib_c.py

	modified:   display/__init__.py
	new file:   display/dpflib_c.py
	new file:   display/pearldisplay_c.py
	new file:   pydpflib_c/Makefile
	new file:   pydpflib_c/py_device.c
'''
@author: irimi_at_gmx_de

   python dpflib port
   based on dpflib.c by  12/2010 <hackfin@section5.ch>

 Additional packages

  - Python Imaging Library (PIL)
  - PyUSB v0.4

 are required

 History
 =======
 Version 0.1  2012-02-19 by irimi_at_gmx_de
 Version 0.2  2012-03-29 by irimi_at_gmx_de : bugfix and convert feature added

'''

import usb
import Image

#SPM_RDID=   0x9f    # Read ID
#SPM_WREN=   0x06    # Write enable
#SPM_WRDI=   0x04    # Write disable
#SPM_RDSR=   0x05    # Read status register
#SPM_WRSR=   0x01    # Write status register
SPM_READ=   0x03    # Read data bytes
#SPM_PP=     0x02    # Page program
#SPM_DP=     0xb9    # Deep power down
#SPM_RES=    0xab    # Release from deep power down
#SPM_SR_SRWD=0x80    # SR write protection (HW)

#SPM_FLASH_SE        =  0xd8    # Sector erase
#SPM_FLASH_BE        =  0xc7    # Bulk erase
#SPM_FLASH_FAST_READ =  0x0B    # Read data bytes fast

#PROTOCOL_VERSION  = 1

# Our vendor specific USB commands to do stuff on the DPF

#USBCMD_GETPROPERTY  =0x00    # Get property
#USBCMD_SETPROPERTY  =0x01    # Set property
#USBCMD_MEMREAD      =0x04    # Memory read
#USBCMD_APPLOAD      =0x05    # Load and run applet
#USBCMD_CLRFB        =0x10    # Clear screen with RGB565 color
#USBCMD_WRITEFB      =0x11    # Write full screen. DEPRECATED.
#USBCMD_BLIT         =0x12    # Blit to screen
#USBCMD_FLASHLOCK    =0x20    # Lock USB for flash access
#USBCMD_PROBE        =0xff    # Get version code (probe)

# Some special return codes
#USB_IN_SEQUENCE     =0x7f    # We're inside a command sequence

# Property handling:
#PROPERTY_BRIGHTNESS  =0x01
#PROPERTY_ORIENTATION =0x10

#DEVERR_FILE     = -16
#DEVERR_OPEN     = -15
#DEVERR_HEX      = -14
#DEVERR_CHK      = -13
#DEVERR_IO       = -12
#DEVERR_MALLOC   = -11
DEVERR_TIMEOUT  = -10

OFFSET_RESOLUTION = 0x23

DIR_IN  = 0
DIR_OUT = 1

ENDPT_OUT = 0x1
ENDPT_IN = 0x81

class dpflib(object):
    '''
    DPF access library for AX206 based HW
    '''

    def __init__(self,idVendor, idProduct,name="dpflib"):

        self.__cmd = [0xcd, 0, 0, 0, 0, 0,
                      0x04, # num of bytes:
                      0, 0, 0,
                      SPM_READ, # SPI offset:
                      0, 0, 0,
                      0, 0
                      ]

        self.__g_buf= [
                         0x55, 0x53, 0x42, 0x43, # dCBWSignature
                         0xaf, 0x00, 0x00, 0x00, # dCBWTag
                         0x00, 0x80, 0x00, 0x00, # dCBWLength
                         0x00, # bmCBWFlags: 0x80: data in (dev to host), 0x00: Data out
                         0x00, # bCBWLUN
                         0x10, # bCBWCBLength
                         # SCSI cmd:
                         0xcd, 0x00, 0x00, 0x00,
                         0x00, 0x06, 0x11, 0xf8,
                         0x70, 0x00, 0x40, 0x00,
                         0x00, 0x00, 0x00, 0x00,
                      ]

        self.__g_excmd = [
                        0xcd, 0, 0, 0,
                        0, 6, 0, 0,
                        0, 0, 0, 0,
                        0, 0, 0, 0
                        ]

        #self._dev = usb.core.find(idVendor=idVendor, idProduct=idProduct) pyusb 1.0
        self._dev,address = self._finddev(idVendor=idVendor, idProduct=idProduct)
        self._height = 0
        self._width = 0
        self.name = name
        self._handle = None
        if self._dev:
            #print "DPF Device 0x%x:0x%x found at /dev/bus/usb/%03d/%03d"%(idVendor,idProduct, self._dev.bus,self._dev.address) pyusb 1.0
            print "DPF Device 0x%x:0x%x found at /dev/bus/usb/%s"%(idVendor,idProduct, address)
            try:
                self._handle = self._dev.open()
                self._handle.claimInterface(0)#interface_id
                #self._dev.set_configuration() #pyusb 1.0
                self._query_res()
                self._maxsize=self._width * self._height * 2
            except IOError as detail:
                print detail
                if str(detail).find("permission") != -1:
                    print "\ttry 'sudo chmod 0660 resp. chown of above USB device'"
                    print "\tor adapt your udev rules"

            #self._cfg = self._dev.get_active_configuration()
        else:
            #self._dev = None
            print "Error: no usb device found with vendor ID: 0x%x product ID: 0x%x"%(idVendor,idProduct)

    def close (self):
        if self._handle:
            self._handle.releaseInterface()

    def _finddev(self,idVendor,idProduct,use2nd=False):
        '''
            look for usb device.
            we are using our own search routine due to libusb problem on some linux distros
        '''
        f1st=False
        #print "Scanning USB ..."
        for bus in usb.busses():
            devices = bus.devices
            for dev in devices:
                #print "\tidVendor: 0x%04x" % (dev.idVendor)
                #print "\tidProduct: 0x%04x" % (dev.idProduct)
                if dev.idVendor==idVendor and dev.idProduct == idProduct:
                    if not use2nd and not f1st:
                        return (dev,"%s/%s"%(bus.dirname,dev.filename))
                    else:
                        if f1st:
                            return (dev,"%s/%s"%(bus.dirname,dev.filename))
                        else:
                            f1st = True
        return (None,None)

    def __str__(self):
        sret=  "Display = %s " %self.name
        if self._width and self._height:
            if self._width > self._height:
                sret +="in landscape"
            else:
                sret +="in portrait"

            sret += " mode:\n\tX resolution = %d\n\tY resolution = %d" %(self._width,self._height)
        else:
            sret += "resolution unknown"

        return sret

    def _flash_read(self,offset,size):
        '''
            get dump from DPS's flash memory
        '''

        cmd = self.__cmd[:]

        cmd[9] = (size & 0x00ff )
        cmd[8] = (size & 0xff00 ) >> 8
        cmd[7] = (size & 0xff0000 ) >> 16

        cmd[13] = (offset & 0x00ff )
        cmd[12] = (offset & 0xff00 ) >> 8
        cmd[11] = (offset & 0xff0000 ) >> 16
        return self._emulate_scsi(cmd,DIR_IN, 0, size)

    def _emulate_scsi(self, cmd, out, outdata, block_size):
        '''
            raw usb preparation and usb access
        '''
        g_buf=self.__g_buf[:]
        g_buf[15:15+len(cmd)]=cmd

        g_buf[8]  = (block_size & 0xff )
        g_buf[9]  = (block_size & 0xff00 ) >> 8
        g_buf[10] = (block_size & 0xff0000 ) >> 16
        g_buf[11] = (block_size & 0xff000000 ) >> 24

        # send the command
        #ret = self._dev.write(ENDPT_OUT, g_buf, timeout=1000) pyusb 1.0
        ret = self._handle.bulkWrite(ENDPT_OUT, g_buf, 1000)
        if ret < 0:
            return ret,None

        if out == DIR_OUT:
            data=None
            if len(outdata)>0:
                #ret = self._dev.write(ENDPT_OUT, outdata,timeout=1000) pyusb1.0
                ret = self._handle.bulkWrite(ENDPT_OUT, outdata,1000)
                if ret != block_size:
                    print "Error bulk write"
                    return ret,None

        else:# DIR_IN
            #data = self._dev.read(ENDPT_IN, block_size, timeout=4000) pyusb 1.0
            data = self._handle.bulkRead(ENDPT_IN, block_size, 4000)
            ret = len(data)
            if ret != block_size:
                print "Error bulk data read"
                ret=-1

        # now get ACK:
        retry = 0
        while retry < 5:
            #ansbuf = self._dev.read(ENDPT_IN, 13, timeout=10000)
            ansbuf = self._handle.bulkRead(ENDPT_IN, 13, 10000)
            if len(ansbuf) != 13:
                    print "Error: bulk ACK read"
                    ret = DEVERR_TIMEOUT
                    retry+=1
            else:
                #fix_me: ret keeps DEVERR_TIMEOUT in case of successful retry
                break

        return ret,data

    def _dpf_screen_blit(self,data, row, col, h, w):
        '''
            build the DPF command to show a picture
        '''
        rect2=col + w
        rect3=row + h

        if self._dev:
            #size = height * width
            #size <<= 1
            size = len(data)
            if size > self._maxsize:
                print "Error: wrong data size - drop it"
                return

            cmd = self.__g_excmd[:]

            cmd[6] = 0x12
            cmd[7] = col & 0xff
            cmd[8] = (col & 0xff00 ) >> 8
            cmd[9] = row & 0xff
            cmd[10] = (row & 0xff00 ) >> 8
            cmd[11] = (rect2 - 1) & 0xff
            cmd[12] = ((rect2 - 1) & 0xff00 ) >> 8
            cmd[13] = (rect3 - 1) & 0xff
            cmd[14] = ((rect3 - 1) & 0xff00 ) >> 8
            cmd[15] = 0

            return self._emulate_scsi(cmd,DIR_OUT, data, size)

    def _query_res(self):
        '''
            this function is called during instantiation to get the DPF's resolution
            i.e. it reads the flash and parses the result.
        '''
        # result of 128 byte buffer:
        #0040  40 01 f0 00 52 00 00 50  00 00 00 00 00 04 5a 82   @...R..P ......Z.
        #0050  06 00 73 00 6a b5 04 ff  ff ff ff ff ff 44 50 46   ..s.j... .....DPF
        #0060  76 31 2e 30 ff ff ff ff  ff ff ff ff ff 32 30 30   v1.0.... .....200
        #0070  39 30 35 30 34 ff ff ff  ff ff ff ff ff 4d 61 72   90504... .....Mar
        #0080  20 32 37 20 32 30 31 30  ff ff ff ff ff 30 39 3a    27 2010 .....09:
        #0090  32 33 3a 31 39 00 00 55  4f ef 51 06 5e 50 72 6f   23:19..U O.Q.^Pro
        #00a0  63 54 62 6c 35 0b 30 0b  9c 14 06 00 00 0b 2a 0d   cTbl5.0. ......*.
        #00b0  27 f6 06 00 00 0b 2a 0c  f5 7a 09 00 00 0b 2a 0d   '.....*. .z....*.

        ret, buf = self._flash_read(OFFSET_RESOLUTION, 64)
        if len(buf)>=4:
            self._width = (buf[0]) | (buf[1] << 8)
            self._height = (buf[2]) | (buf[3] << 8)
        return ret

    def _doRGB565(self, row, col, h, w,rgbdata):
        ''' the DPF's required format is 16bit RGB565 we have to convert from 24bit RGB
            this function converts 24bit RGB to 16bit RGB565 and sends it to the DPF
         '''
        r565 = []
#        old , very slow code but easier to read
#        for p in range(row, height * width + col):
#                R,G,B=rgbdata[p]
#                low=(R & 0xf8) |((G & 0xe0) >> 5)
#                high=((G & 0x1c) << 3)|((B & 0xf8) >> 3)
#                r565.append(low) #_RGB565_0(p)
#                r565.append(high)# _RGB565_1(p)

        [(r565.append((p[0] & 0xf8) |((p[1] & 0xe0) >> 5 )),r565.append(((p[1] & 0x1c) << 3)|((p[2] & 0xf8) >> 3)) ) for p in rgbdata]

        self._dpf_screen_blit(r565, row, col, h, w)

    def getRes(self):
        return (self._width,self._height)

    def showImage(self,image,x=0,y=0):
        ''' PIL image instance is required
            Smaller images as DPF resolution can be placed with x,y parameter
        '''
        w,h = image.size
        if w+x>self._width or h+y>self._height:
            w=self._width
            h=self._height
            image=image.resize((self._width -x, self._height-y), Image.BILINEAR)

        #RGB image required
        if image.mode.find("RGB")==-1:
            image=image.convert("RGB")

        self._doRGB565(x, y, h, w, tuple(image.getdata()))

    def clear(self):
        self.showImage(Image.new('RGB', (self._width, self._height), (0, 0, 0, 0)))

class DPFPearl(dpflib):
    '''
    create PERL DPF vendor / product specific instance
    '''
    def __init__(self):
        dpflib.__init__(self,0x1908, 0x0102,"Pearl DPF")

class DPFPearlDemo(DPFPearl):

    def test(self,fd='./gfx/demo.png'):
        if self._width >0:
            try:
                im = Image.open(fd)
            except:
                im = Image.new('RGB', (self._width,self._height), (0, 0, 0, 0))
                draw = ImageDraw.Draw(im)
                draw.text((10, self._height / 2 - 20), "Could not open demo picture: %s"%(fd))
                draw.text((10, self._height / 2), "dpflib.py demo")
                draw.text((10, self._height / 2 + 20), "by irimi_at_gmx_de")

            self.showImage(im,0,0)
            im.show() # 'display' (imagemagick)) or 'xv' has to be installed

            time.sleep(30)
            im = Image.new('RGB', (self._width,self._height), (0, 0, 0, 0))
            draw = ImageDraw.Draw(im)
            draw.text((10, self._height / 2), "Goodbye - CU on your DPF ;-)")
            self.showImage(im,0,0)
            self.close()

def convertRGB565(fd='./gfx/demo.png',width=320,height=240):

    print "Exported Image Size = %d x %d"%(width,height)
    try:
        image = Image.open(fd)
    except:
        print "file not found: using default generated image"
        image = Image.new('RGB', (width,height), (0, 0, 0, 0))
        draw = ImageDraw.Draw(image)
        draw.text((10, height / 2 - 20), "Could not open demo picture: %s"%(fd))
        draw.text((10, height / 2), "dpflib.py demo")
        draw.text((10, height / 2 + 20), "by irimi_at_gmx_de")

    w,h = image.size
    if w>width or h>height:
        image=image.resize((width, height), Image.BILINEAR)# @TODO not relative !

    #RGB image required
    if image.mode.find("RGB")==-1:
        image=image.convert("RGB")

    image.show() # 'display' (imagemagick)) or 'xv' has to be installed
    rgbdata = tuple(image.getdata())
    r565 = []
    [(r565.append((p[0] & 0xf8) |((p[1] & 0xe0) >> 5 )),r565.append(((p[1] & 0x1c) << 3)|((p[2] & 0xf8) >> 3)) ) for p in rgbdata]

    f = open("./converted.c", "wb")

    i=0
    ii=0
    sz=len(r565)
    f.write( "g_gfx565_%dX%s_%d={\n\t"%(width,height,sz)),
    for outx in r565:
        f.write("0x%02x"%(outx)),
        ii+=1
        i+=1
        if i<sz:
            f.write( ",")
        if ii > 16:
            f.write( "\n\t"),
            ii=0
    f.write( "\n\t};")
    f.close()

def usage():
    print "Usage:\n\t%s ['filename'] || [convert] || ['filename' convert] || ['filename' convert 'width' 'height']"%(sys.argv[0])
    print "\nconverted data will be saved to ./converted.c"
    print "example: 'python ./dpflib.py ../gfx/demo.png convert 160 120' - to convert a picture"
    print "example: 'python ./dpflib.py ../gfx/demo.png' - to display picture on your DPF"
    print "\nInstalled imagemagick or xv is required if picture shall be display on your PC monitor, too"

if __name__ == '__main__':

    import sys
    import time
    import ImageDraw

    cnt=len(sys.argv)

    if cnt > 1:
        if (cnt == 2) and sys.argv[1] == "convert":
            convertRGB565()
        elif (cnt==5) and sys.argv[2] == "convert":
            convertRGB565(sys.argv[1],int(sys.argv[3]),int(sys.argv[4]))
        elif cnt == 2 and sys.argv[1] == "help":
            usage()
        elif cnt == 2:
            dpf=DPFPearlDemo()
            print dpf
            dpf.test(sys.argv[1])
        elif cnt==3 and sys.argv[2] == "convert":
            convertRGB565(sys.argv[1])
        else:
            usage()
    else:
        dpf=DPFPearlDemo()
        print dpf
        dpf.test()
'''
@author: irimi_at_gmx_de

   python dpflib port
   based on dpflib.c by  12/2010 <hackfin@section5.ch>

 Additional packages

  - Python Imaging Library (PIL)
  - PyUSB v0.4

 are required

 History
 =======
 Version 0.1  2012-02-19 by irimi_at_gmx_de
 Version 0.2  2012-03-29 by irimi_at_gmx_de : bugfix and convert feature added

'''

import usb
import Image

#SPM_RDID=   0x9f    # Read ID
#SPM_WREN=   0x06    # Write enable
#SPM_WRDI=   0x04    # Write disable
#SPM_RDSR=   0x05    # Read status register
#SPM_WRSR=   0x01    # Write status register
SPM_READ=   0x03    # Read data bytes
#SPM_PP=     0x02    # Page program
#SPM_DP=     0xb9    # Deep power down
#SPM_RES=    0xab    # Release from deep power down
#SPM_SR_SRWD=0x80    # SR write protection (HW)

#SPM_FLASH_SE        =  0xd8    # Sector erase
#SPM_FLASH_BE        =  0xc7    # Bulk erase
#SPM_FLASH_FAST_READ =  0x0B    # Read data bytes fast

#PROTOCOL_VERSION  = 1

# Our vendor specific USB commands to do stuff on the DPF

#USBCMD_GETPROPERTY  =0x00    # Get property
#USBCMD_SETPROPERTY  =0x01    # Set property
#USBCMD_MEMREAD      =0x04    # Memory read
#USBCMD_APPLOAD      =0x05    # Load and run applet
#USBCMD_CLRFB        =0x10    # Clear screen with RGB565 color
#USBCMD_WRITEFB      =0x11    # Write full screen. DEPRECATED.
#USBCMD_BLIT         =0x12    # Blit to screen
#USBCMD_FLASHLOCK    =0x20    # Lock USB for flash access
#USBCMD_PROBE        =0xff    # Get version code (probe)

# Some special return codes
#USB_IN_SEQUENCE     =0x7f    # We're inside a command sequence

# Property handling:
#PROPERTY_BRIGHTNESS  =0x01
#PROPERTY_ORIENTATION =0x10

#DEVERR_FILE     = -16
#DEVERR_OPEN     = -15
#DEVERR_HEX      = -14
#DEVERR_CHK      = -13
#DEVERR_IO       = -12
#DEVERR_MALLOC   = -11
DEVERR_TIMEOUT  = -10

OFFSET_RESOLUTION = 0x23

DIR_IN  = 0
DIR_OUT = 1

ENDPT_OUT = 0x1
ENDPT_IN = 0x81

class dpflib(object):
    '''
    DPF access library for AX206 based HW
    '''

    def __init__(self,idVendor, idProduct,name="dpflib"):

        self.__cmd = [0xcd, 0, 0, 0, 0, 0,
                      0x04, # num of bytes:
                      0, 0, 0,
                      SPM_READ, # SPI offset:
                      0, 0, 0,
                      0, 0
                      ]

        self.__g_buf= [
                         0x55, 0x53, 0x42, 0x43, # dCBWSignature
                         0xaf, 0x00, 0x00, 0x00, # dCBWTag
                         0x00, 0x80, 0x00, 0x00, # dCBWLength
                         0x00, # bmCBWFlags: 0x80: data in (dev to host), 0x00: Data out
                         0x00, # bCBWLUN
                         0x10, # bCBWCBLength
                         # SCSI cmd:
                         0xcd, 0x00, 0x00, 0x00,
                         0x00, 0x06, 0x11, 0xf8,
                         0x70, 0x00, 0x40, 0x00,
                         0x00, 0x00, 0x00, 0x00,
                      ]

        self.__g_excmd = [
                        0xcd, 0, 0, 0,
                        0, 6, 0, 0,
                        0, 0, 0, 0,
                        0, 0, 0, 0
                        ]

        #self._dev = usb.core.find(idVendor=idVendor, idProduct=idProduct) pyusb 1.0
        self._dev,address = self._finddev(idVendor=idVendor, idProduct=idProduct)
        self._height = 0
        self._width = 0
        self.name = name
        self._handle = None
        if self._dev:
            #print "DPF Device 0x%x:0x%x found at /dev/bus/usb/%03d/%03d"%(idVendor,idProduct, self._dev.bus,self._dev.address) pyusb 1.0
            print "DPF Device 0x%x:0x%x found at /dev/bus/usb/%s"%(idVendor,idProduct, address)
            try:
                self._handle = self._dev.open()
                self._handle.claimInterface(0)#interface_id
                #self._dev.set_configuration() #pyusb 1.0
                self._query_res()
                self._maxsize=self._width * self._height * 2
            except IOError as detail:
                print detail
                if str(detail).find("permission") != -1:
                    print "\ttry 'sudo chmod 0660 resp. chown of above USB device'"
                    print "\tor adapt your udev rules"

            #self._cfg = self._dev.get_active_configuration()
        else:
            #self._dev = None
            print "Error: no usb device found with vendor ID: 0x%x product ID: 0x%x"%(idVendor,idProduct)

    def close (self):
        if self._handle:
            self._handle.releaseInterface()

    def _finddev(self,idVendor,idProduct,use2nd=False):
        '''
            look for usb device.
            we are using our own search routine due to libusb problem on some linux distros
        '''
        f1st=False
        #print "Scanning USB ..."
        for bus in usb.busses():
            devices = bus.devices
            for dev in devices:
                #print "\tidVendor: 0x%04x" % (dev.idVendor)
                #print "\tidProduct: 0x%04x" % (dev.idProduct)
                if dev.idVendor==idVendor and dev.idProduct == idProduct:
                    if not use2nd and not f1st:
                        return (dev,"%s/%s"%(bus.dirname,dev.filename))
                    else:
                        if f1st:
                            return (dev,"%s/%s"%(bus.dirname,dev.filename))
                        else:
                            f1st = True
        return (None,None)

    def __str__(self):
        sret=  "Display = %s " %self.name
        if self._width and self._height:
            if self._width > self._height:
                sret +="in landscape"
            else:
                sret +="in portrait"

            sret += " mode:\n\tX resolution = %d\n\tY resolution = %d" %(self._width,self._height)
        else:
            sret += "resolution unknown"

        return sret

    def _flash_read(self,offset,size):
        '''
            get dump from DPS's flash memory
        '''

        cmd = self.__cmd[:]

        cmd[9] = (size & 0x00ff )
        cmd[8] = (size & 0xff00 ) >> 8
        cmd[7] = (size & 0xff0000 ) >> 16

        cmd[13] = (offset & 0x00ff )
        cmd[12] = (offset & 0xff00 ) >> 8
        cmd[11] = (offset & 0xff0000 ) >> 16
        return self._emulate_scsi(cmd,DIR_IN, 0, size)

    def _emulate_scsi(self, cmd, out, outdata, block_size):
        '''
            raw usb preparation and usb access
        '''
        g_buf=self.__g_buf[:]
        g_buf[15:15+len(cmd)]=cmd

        g_buf[8]  = (block_size & 0xff )
        g_buf[9]  = (block_size & 0xff00 ) >> 8
        g_buf[10] = (block_size & 0xff0000 ) >> 16
        g_buf[11] = (block_size & 0xff000000 ) >> 24

        # send the command
        #ret = self._dev.write(ENDPT_OUT, g_buf, timeout=1000) pyusb 1.0
        ret = self._handle.bulkWrite(ENDPT_OUT, g_buf, 1000)
        if ret < 0:
            return ret,None

        if out == DIR_OUT:
            data=None
            if len(outdata)>0:
                #ret = self._dev.write(ENDPT_OUT, outdata,timeout=1000) pyusb1.0
                ret = self._handle.bulkWrite(ENDPT_OUT, outdata,1000)
                if ret != block_size:
                    print "Error bulk write"
                    return ret,None

        else:# DIR_IN
            #data = self._dev.read(ENDPT_IN, block_size, timeout=4000) pyusb 1.0
            data = self._handle.bulkRead(ENDPT_IN, block_size, 4000)
            ret = len(data)
            if ret != block_size:
                print "Error bulk data read"
                ret=-1

        # now get ACK:
        retry = 0
        while retry < 5:
            #ansbuf = self._dev.read(ENDPT_IN, 13, timeout=10000)
            ansbuf = self._handle.bulkRead(ENDPT_IN, 13, 10000)
            if len(ansbuf) != 13:
                    print "Error: bulk ACK read"
                    ret = DEVERR_TIMEOUT
                    retry+=1
            else:
                #fix_me: ret keeps DEVERR_TIMEOUT in case of successful retry
                break

        return ret,data

    def _dpf_screen_blit(self,data, row, col, h, w):
        '''
            build the DPF command to show a picture
        '''
        rect2=col + w
        rect3=row + h

        if self._dev:
            #size = height * width
            #size <<= 1
            size = len(data)
            if size > self._maxsize:
                print "Error: wrong data size - drop it"
                return

            cmd = self.__g_excmd[:]

            cmd[6] = 0x12
            cmd[7] = col & 0xff
            cmd[8] = (col & 0xff00 ) >> 8
            cmd[9] = row & 0xff
            cmd[10] = (row & 0xff00 ) >> 8
            cmd[11] = (rect2 - 1) & 0xff
            cmd[12] = ((rect2 - 1) & 0xff00 ) >> 8
            cmd[13] = (rect3 - 1) & 0xff
            cmd[14] = ((rect3 - 1) & 0xff00 ) >> 8
            cmd[15] = 0

            return self._emulate_scsi(cmd,DIR_OUT, data, size)

    def _query_res(self):
        '''
            this function is called during instantiation to get the DPF's resolution
            i.e. it reads the flash and parses the result.
        '''
        # result of 128 byte buffer:
        #0040  40 01 f0 00 52 00 00 50  00 00 00 00 00 04 5a 82   @...R..P ......Z.
        #0050  06 00 73 00 6a b5 04 ff  ff ff ff ff ff 44 50 46   ..s.j... .....DPF
        #0060  76 31 2e 30 ff ff ff ff  ff ff ff ff ff 32 30 30   v1.0.... .....200
        #0070  39 30 35 30 34 ff ff ff  ff ff ff ff ff 4d 61 72   90504... .....Mar
        #0080  20 32 37 20 32 30 31 30  ff ff ff ff ff 30 39 3a    27 2010 .....09:
        #0090  32 33 3a 31 39 00 00 55  4f ef 51 06 5e 50 72 6f   23:19..U O.Q.^Pro
        #00a0  63 54 62 6c 35 0b 30 0b  9c 14 06 00 00 0b 2a 0d   cTbl5.0. ......*.
        #00b0  27 f6 06 00 00 0b 2a 0c  f5 7a 09 00 00 0b 2a 0d   '.....*. .z....*.

        ret, buf = self._flash_read(OFFSET_RESOLUTION, 64)
        if len(buf)>=4:
            self._width = (buf[0]) | (buf[1] << 8)
            self._height = (buf[2]) | (buf[3] << 8)
        return ret

    def _doRGB565(self, row, col, h, w,rgbdata):
        ''' the DPF's required format is 16bit RGB565 we have to convert from 24bit RGB
            this function converts 24bit RGB to 16bit RGB565 and sends it to the DPF
         '''
        r565 = []
#        old , very slow code but easier to read
#        for p in range(row, height * width + col):
#                R,G,B=rgbdata[p]
#                low=(R & 0xf8) |((G & 0xe0) >> 5)
#                high=((G & 0x1c) << 3)|((B & 0xf8) >> 3)
#                r565.append(low) #_RGB565_0(p)
#                r565.append(high)# _RGB565_1(p)

        [(r565.append((p[0] & 0xf8) |((p[1] & 0xe0) >> 5 )),r565.append(((p[1] & 0x1c) << 3)|((p[2] & 0xf8) >> 3)) ) for p in rgbdata]

        self._dpf_screen_blit(r565, row, col, h, w)

    def getRes(self):
        return (self._width,self._height)

    def showImage(self,image,x=0,y=0):
        ''' PIL image instance is required
            Smaller images as DPF resolution can be placed with x,y parameter
        '''
        w,h = image.size
        if w+x>self._width or h+y>self._height:
            w=self._width
            h=self._height
            image=image.resize((self._width -x, self._height-y), Image.BILINEAR)

        #RGB image required
        if image.mode.find("RGB")==-1:
            image=image.convert("RGB")

        self._doRGB565(x, y, h, w, tuple(image.getdata()))

    def clear(self):
        self.showImage(Image.new('RGB', (self._width, self._height), (0, 0, 0, 0)))

class DPFPearl(dpflib):
    '''
    create PERL DPF vendor / product specific instance
    '''
    def __init__(self):
        dpflib.__init__(self,0x1908, 0x0102,"Pearl DPF")

class DPFPearlDemo(DPFPearl):

    def test(self,fd='./gfx/demo.png'):
        if self._width >0:
            try:
                im = Image.open(fd)
            except:
                im = Image.new('RGB', (self._width,self._height), (0, 0, 0, 0))
                draw = ImageDraw.Draw(im)
                draw.text((10, self._height / 2 - 20), "Could not open demo picture: %s"%(fd))
                draw.text((10, self._height / 2), "dpflib.py demo")
                draw.text((10, self._height / 2 + 20), "by irimi_at_gmx_de")

            self.showImage(im,0,0)
            im.show() # 'display' (imagemagick)) or 'xv' has to be installed

            time.sleep(30)
            im = Image.new('RGB', (self._width,self._height), (0, 0, 0, 0))
            draw = ImageDraw.Draw(im)
            draw.text((10, self._height / 2), "Goodbye - CU on your DPF ;-)")
            self.showImage(im,0,0)
            self.close()

def convertRGB565(fd='./gfx/demo.png',width=320,height=240):

    print "Exported Image Size = %d x %d"%(width,height)
    try:
        image = Image.open(fd)
    except:
        print "file not found: using default generated image"
        image = Image.new('RGB', (width,height), (0, 0, 0, 0))
        draw = ImageDraw.Draw(image)
        draw.text((10, height / 2 - 20), "Could not open demo picture: %s"%(fd))
        draw.text((10, height / 2), "dpflib.py demo")
        draw.text((10, height / 2 + 20), "by irimi_at_gmx_de")

    w,h = image.size
    if w>width or h>height:
        image=image.resize((width, height), Image.BILINEAR)# @TODO not relative !

    #RGB image required
    if image.mode.find("RGB")==-1:
        image=image.convert("RGB")

    image.show() # 'display' (imagemagick)) or 'xv' has to be installed
    rgbdata = tuple(image.getdata())
    r565 = []
    [(r565.append((p[0] & 0xf8) |((p[1] & 0xe0) >> 5 )),r565.append(((p[1] & 0x1c) << 3)|((p[2] & 0xf8) >> 3)) ) for p in rgbdata]

    f = open("./converted.c", "wb")

    i=0
    ii=0
    sz=len(r565)
    f.write( "g_gfx565_%dX%s_%d={\n\t"%(width,height,sz)),
    for outx in r565:
        f.write("0x%02x"%(outx)),
        ii+=1
        i+=1
        if i<sz:
            f.write( ",")
        if ii > 16:
            f.write( "\n\t"),
            ii=0
    f.write( "\n\t};")
    f.close()

def usage():
    print "Usage:\n\t%s ['filename'] || [convert] || ['filename' convert] || ['filename' convert 'width' 'height']"%(sys.argv[0])
    print "\nconverted data will be saved to ./converted.c"
    print "example: 'python ./dpflib.py ../gfx/demo.png convert 160 120' - to convert a picture"
    print "example: 'python ./dpflib.py ../gfx/demo.png' - to display picture on your DPF"
    print "\nInstalled imagemagick or xv is required if picture shall be display on your PC monitor, too"

if __name__ == '__main__':

    import sys
    import time
    import ImageDraw

    cnt=len(sys.argv)

    if cnt > 1:
        if (cnt == 2) and sys.argv[1] == "convert":
            convertRGB565()
        elif (cnt==5) and sys.argv[2] == "convert":
            convertRGB565(sys.argv[1],int(sys.argv[3]),int(sys.argv[4]))
        elif cnt == 2 and sys.argv[1] == "help":
            usage()
        elif cnt == 2:
            dpf=DPFPearlDemo()
            print dpf
            dpf.test(sys.argv[1])
        elif cnt==3 and sys.argv[2] == "convert":
            convertRGB565(sys.argv[1])
        else:
            usage()
    else:
        dpf=DPFPearlDemo()
        print dpf
        dpf.test()