Thursday, January 12, 2012

Code to switch the frame from Mass Storage mode to Mini Monitor mode

A picture can only be written to the photo frame when it is in Mini Monitor mode (and it is INITialized). However, when it is found in Mass Monitor mode, it can be switched to Mini Monitor mode by a script. The code reqired is:
dev.ctrl_transfer(0x00|0x80,  0x06, 0xfe, 0xfe, 0xfe )
Settling on the USB bus takes <0.5sec, but give it some extra time.

A stripped down version of a program which takes care of switching and initialization and can be fed with pictures of any size and (almost any) type is following. Note the use of the Image module for image manipulation, and of StringIO to avoid writing and reading temp files to/from disk:

# -*- coding: UTF-8 -*-

import os
import sys
import time
import usb.core
import usb.util
import StringIO
import Image
import struct

def write_jpg2frame(dev, pic):
    """Attach header to picture, pad with zeros if necessary, and send to frame"""
    # create header and stack before picture
    # middle 4 bytes have size of picture
    rawdata = b"\xa5\x5a\x18\x04" + struct.pack('<I', len(pic)) + b"\x48\x00\x00\x00" + pic
    # total transfers must be complete chunks of 16384  = 2 ^14. Complete by padding with zeros
    pad = 16384 - (len(rawdata) % 16384) +1         
    tdata = rawdata + pad * b'\x00'
    ltdata = len(tdata)
    # Syntax: write(self, endpoint, data, interface = None, timeout = None):
    endpoint = 0x02               
    dev.write(endpoint, tdata )

def get_known_devices():
    """Return a dict of photo frames"""
    # listed as: Name, idVendor, idProduct, [width , height - in pixel if applicable]
    # Samsung SPF-87H in either mini monitor mode or mass storage mode
    SPF87H_MiniMon   = {'name':"SPF87H Mini Monitor", 'idVendor':0x04e8, 'idProduct':0x2034, 'width':800, 'height':480 }
    SPF87H_MassSto   = {'name':"SPF87H Mass Storage", 'idVendor':0x04e8, 'idProduct':0x2033}
    # Samsung SPF-107H (data from web reports - not tested)
    SPF107H_MiniMon  = {'name':"SPF107H Mini Monitor", 'idVendor':0x04e8, 'idProduct':0x2036, 'width':1024, 'height':600 }
    SPF107H_MassSto  = {'name':"SPF107H Mass Storage", 'idVendor':0x04e8, 'idProduct':0x2035}
    # Samsung SPF-83H (data from web reports - not tested)
    SPF107H_MiniMon  = {'name':"SPF107H Mini Monitor", 'idVendor':0x04e8, 'idProduct':0x200d, 'width':800, 'height':600 }
    SPF107H_MassSto  = {'name':"SPF107H Mass Storage", 'idVendor':0x04e8, 'idProduct':0x200c}
    return    ( SPF87H_MiniMon, SPF87H_MassSto, SPF107H_MiniMon, SPF107H_MassSto, SPF107H_MiniMon, SPF107H_MassSto )

def find_device(device):
    """Try to find device on USB bus."""
    return usb.core.find(idVendor=device['idVendor'], idProduct=device['idProduct'])   

def init_device(device0, device1):
    """First try Mini Monitor mode, then Mass storage mode"""
    dev = find_device(device0)
    if dev is not None:
        ## found it, trying to init it
        # not found device in Mini Monitor mode, trying to find it in Mass Storage mode
        dev = find_device(device1)
        if dev is not None:
            #found it in Mass Storage, trying to switch to Mini Monitor
            ts = time.time()
            while True:
                # may need to burn some time
                dev = find_device(device0)
                if dev is not None:
                    #switching successful
                elif time.time() - ts > 2:
                    print "switching failed. Ending program"
            print "Could not find frame in either mode"
    return dev

def frame_init(dev):
    """Init device so it stays in Mini Monitor mode"""
    # this is the minimum required to keep the frame in Mini Monitor mode!!!
    dev.ctrl_transfer(0xc0, 4 )   

def frame_switch(dev):
    """Switch device from Mass Storage to Mini Monitor""" 
    dev.ctrl_transfer(0x00|0x80,  0x06, 0xfe, 0xfe, 0xfe )
    # settling of the bus and frame takes about 0.42 sec
    # give it some extra time, but then still make sure it has settled

def main():
    global dev, known_devices_list
    known_devices_list = get_known_devices()

    # define which frame to use, here use Samsung SPF-87H
    device0 = known_devices_list[0] # Mini Monitor mode
    device1 = known_devices_list[1] # Mass Storage mode

    dev = init_device(device0, device1)   
    print "Frame is in Mini Monitor mode and initialized. Sending pictures now"

    image ="mypicture.jpg")
    #manipulations to consider:
    #  convert
    #  thumbnail
    #  rotate
    #  crop
    image = image.resize((800,480))
    output = StringIO.StringIO(), "JPEG", quality=94)
    pic  = output.getvalue()
    write_jpg2frame(dev, pic)       

if __name__ == "__main__":


  1. Well, i tried this. but i always get:

    Traceback (most recent call last):
    File "", line 122, in
    File "", line 104, in main
    dev = init_device(device0, device1)
    File "", line 63, in init_device
    File "", line 89, in frame_switch
    dev.ctrl_transfer(0x00|0x80, 0x06, 0xfe, 0xfe, 0xfe )
    File "/usr/local/lib/python2.7/dist-packages/usb/", line 1043, in ctrl_transfer
    File "/usr/local/lib/python2.7/dist-packages/usb/backend/", line 883, in ctrl_transfer
    File "/usr/local/lib/python2.7/dist-packages/usb/backend/", line 595, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
    usb.core.USBError: [Errno 32] Pipe error

    1. Forget it. Works like a charm, if you do not try to use a ubuntu in a VMware... One thing to know: 'mypicture.jpg' has to be renamed.