Thursday, December 15, 2011

pyframe_basic - the program

#!/usr/bin/python

# To use the 'Samsung SPF-87H Digital Photo Frame' in Mini Monitor mode, e.g. to serve as a
# second monitor use 'pyframe_basic' as the most simple script for basic functionality
# Tested under Linux (Ubuntu Lucid) with Python 2.6.5 and 3.2.2+. Expected to also work
# under Windows, but that was not tested
#
# usage: /path/to/pyframe_basic  [/path/to/picture.jpg]
#
#   The frame then displays picture.jpg. If none is provided, the frame remains unchanged.
#   The frame does ONLY show JPEG pictures, therefore any content must be provided as a
#   JPEG picture, strictly with the frame resolution of 800x480 pixel (width x height)
#
# To avoid having to run a script as root (required for write access to USB) it is suggested
# to define a udev rule. This has the additional benefit that the frame will be caught as
# soon as it is connected in Mini Monitor mode, and it will be hold in this mode permanently
# (and show a welcome.jpg picture if provided). Then the script can be used as a regular user:
#
#   As root create a file '50-samsung_SPF87H.rules' in '/etc/udev/rules.d' with the content:
#   
#      SYSFS{idVendor}=="04e8", SYSFS{idProduct}=="2034", MODE="0666", OWNER="root",
#      GROUP="plugdev" RUN+="/path/to/pyframe_basic /path/to/welcome.jpg"

#   (Don't forget to replace the two '/path/to/' with the paths of your installation!)   
#   In Ubuntu Lucid the the regular users do belong to the group plugdev. Verify that they also
#   do in your version. If not create the group. It might also work to select a different group
#   to which everybody belongs.
#
#   Execute 'sudo restart udev' to activate the new rule.

# The Samsung SPF-87H Digital Photo Frame :
#   is an 8" frame with a resolution of 800x480 pixel (width x height)
#   in Mini-Monitor mode has the IDs on the USB bus: idVendor:0x04e8, idProduct:0x2034
#   in Mass storage mode has the IDs on the USB bus: idVendor:0x04e8, idProduct:0x2033
#   in Photo Frame mode is NOT registered on the USB bus
#   The firmware in use is 1007.0 (full version name: M-IR8HSBWW-1007.0)
#   copyright ullix


import sys
import struct

# The PyUSB 1.0 module is needed, available here: http://sourceforge.net/apps/trac/pyusb/
# Note: Do NOT use the python-usb package from the Ubuntu Lucid repositories,
# since this is incompatible version 0.4! If already installed, then uninstall first before
# installing PyUSB 1.0 !
import usb.core

device = "SPF87H Mini Monitor"

# This function searches the USB bus for a device with the specified IDs.
# If found an object is returned representing the device,
# if not found, then 'None' is returned
dev = usb.core.find(idVendor=0x04e8, idProduct=0x2034)

if dev is None:
    # SPF87H Mini Monitor not found.
    # Remember the frame must be connected AND be switched to Mini Monitor mode at the frame,
    # before calling this script!
    print "Could not find", device, " - Exiting\n"
    sys.exit()

print "Found", device

# This control function is required to keep the frame permanently in Mini Monitor mode.
# When the frame is switched to Mini Monitor mode, it remains there for a few seconds, but
# then switches back, unregistering from the USB bus.
# This function is needed only once after connection of the frame, but it does not seem
# to do any harm when transfered with every picture transfer
dev.ctrl_transfer(0xc0, 4 )   

# If no picture is given on the command line, then stop here.
# (first argument of sys.argv is the filename of the script)
if len(sys.argv) < 2:
    print "No picture given  - Exiting."
    sys.exit()

filename = sys.argv[1]
print "Picture to show is:", filename

# Open the picture file and read into a string
infile = open(filename, "rb")
pic = infile.read()
infile.close()

# The photo frame expects a header of 12 bytes, followed by the picture data.
# The first 4 and the last 4 bytes are always the same.
# The middle 4 bytes are the picture size (excluding the header) with the least significant byte first
rawdata = b"\xa5\x5a\x18\x04" + struct.pack('<I', len(pic)) + b"\x48\x00\x00\x00" + pic


# The photo frame expects transfers in complete chunks of 16384 bytes (=2^14 bytes).
# If last chunk of rawdata is not complete, then make it complete by padding with zeros.
pad = 16384 - (len(rawdata) % 16384)
tdata = rawdata + pad * b'\x00'

# For unknown reasons, some pictures will only transfer successfully, when at least one
# additional zero byte is added. Possibly a firmware bug of the frame?
tdata = tdata + b'\x00'

# Write the data. Must write to USB endpoint 2
endpoint = 0x02
bytes_written = dev.write(endpoint, tdata )

print "Have written", bytes_written, "bytes to photo frame"

5 comments:

  1. Thanks! All working as expected on my system (archlinux, pyusb 1.0.0_a2-1). By the way, the color depth of an image must be set to 16 (convert -treedepth 16 1.jpg 2.jpg for example), otherwise only mess of colors is displayed.

    ReplyDelete
    Replies
    1. Thanks. From which module comes the "convert"?
      ullix

      Delete
    2. pacman -Qo /usr/bin/convert
      /usr/bin/convert is owned by imagemagick 6.7.4.0-1

      Delete
    3. Ah, an external program. I first thought is was a python function.

      Note my new postings of today. Using the Image module to read in a picture file not only allows image manipulations (resizing, cropping, rotating,...) for images of any size and type, and in my tests also can read 16bit color depth and produce the correct 8 bit output. This posting has a simple script: http://pyframe.blogspot.com/2012/01/code-to-switch-frame-from-mass-storage.html
      I'd appreciate if you could give it a try for your problem and report back, thanks

      Delete
    4. Yeah, great, of course. I will try it on this weekend I guess and surely report back.

      Delete