mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Added working support for the following commands to libprs500:
- ls -lhR --color - cp from device to host - info (get device information) - cat files on device Added a command line interface in prs500.py Added documentation in epytext Added support for distutils
This commit is contained in:
parent
5a57a2c022
commit
1c8319a5a4
5
MANIFEST.in
Normal file
5
MANIFEST.in
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
include libprs500 *.py
|
||||||
|
include scripts *.py
|
||||||
|
include README
|
||||||
|
include docs/pdf/api.pdf
|
||||||
|
recursive-include docs/html *
|
8
Makefile.distrib
Normal file
8
Makefile.distrib
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
all: doc tarball
|
||||||
|
|
||||||
|
tarball:
|
||||||
|
python setup.py sdist --formats=gztar,zip
|
||||||
|
|
||||||
|
doc:
|
||||||
|
epydoc --config epydoc.conf
|
||||||
|
epydoc -v --config epydoc-pdf.conf
|
13
README
13
README
@ -4,13 +4,16 @@ Requirements:
|
|||||||
1) Python >= 2.5
|
1) Python >= 2.5
|
||||||
2) PyUSB >= 0.3.4 (http://sourceforge.net/projects/pyusb/)
|
2) PyUSB >= 0.3.4 (http://sourceforge.net/projects/pyusb/)
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
As root
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
At the moment all that it can do is a simple ls command. Add the following to /etc/udev/rules.d/90-local.rules
|
Add the following to /etc/udev/rules.d/90-local.rules
|
||||||
|
|
||||||
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
||||||
|
|
||||||
and run udevstart to enable access to the reader for non-root users. You may have to adjust the GROUP and the location of the
|
and run udevstart to enable access to the reader for non-root users. You may have to adjust the GROUP and the location of the
|
||||||
rules file to suit your distribution.
|
rules file to suit your distribution.
|
||||||
|
|
||||||
To see the listing
|
Usage information is provided when you run the script prs500.py
|
||||||
./communicate.py /path/to/see
|
|
||||||
|
|
||||||
If the path does not exist, it will throw an Exception.
|
|
||||||
|
405
communicate.py
405
communicate.py
@ -1,405 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
|
||||||
## 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.,
|
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
|
|
||||||
### End point description for PRS-500 procductId=667
|
|
||||||
### Endpoint Descriptor:
|
|
||||||
### bLength 7
|
|
||||||
### bDescriptorType 5
|
|
||||||
### bEndpointAddress 0x81 EP 1 IN
|
|
||||||
### bmAttributes 2
|
|
||||||
### Transfer Type Bulk
|
|
||||||
### Synch Type None
|
|
||||||
### Usage Type Data
|
|
||||||
### wMaxPacketSize 0x0040 1x 64 bytes
|
|
||||||
### bInterval 0
|
|
||||||
### Endpoint Descriptor:
|
|
||||||
### bLength 7
|
|
||||||
### bDescriptorType 5
|
|
||||||
### bEndpointAddress 0x02 EP 2 OUT
|
|
||||||
### bmAttributes 2
|
|
||||||
### Transfer Type Bulk
|
|
||||||
### Synch Type None
|
|
||||||
### Usage Type Data
|
|
||||||
### wMaxPacketSize 0x0040 1x 64 bytes
|
|
||||||
### bInterval 0
|
|
||||||
###
|
|
||||||
###
|
|
||||||
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
|
||||||
### Has two configurations 1 is the USB charging config 2 is the self-powered config.
|
|
||||||
### I think config management is automatic. Endpoints are the same
|
|
||||||
|
|
||||||
import sys, usb, logging, StringIO, time
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
from prstypes import *
|
|
||||||
from errors import *
|
|
||||||
from terminfo import TerminalController
|
|
||||||
|
|
||||||
#try:
|
|
||||||
# import psyco
|
|
||||||
# psyco.full()
|
|
||||||
#except ImportError:
|
|
||||||
# print 'Psyco not installed, the program will just run slower'
|
|
||||||
_term = None
|
|
||||||
|
|
||||||
LOG_PACKETS=False # If True all packets are looged to stdout
|
|
||||||
MINIMUM_COL_WIDTH = 12
|
|
||||||
|
|
||||||
class File(object):
|
|
||||||
def __init__(self, file):
|
|
||||||
self.is_dir = file[1].is_dir
|
|
||||||
self.is_readonly = file[1].is_readonly
|
|
||||||
self.size = file[1].file_size
|
|
||||||
self.ctime = file[1].ctime
|
|
||||||
self.wtime = file[1].wtime
|
|
||||||
path = file[0]
|
|
||||||
if path.endswith("/"): path = path[:-1]
|
|
||||||
self.path = path
|
|
||||||
self.name = path[path.rfind("/")+1:].rstrip()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.path
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def mode_string():
|
|
||||||
doc=""" The mode string for this file. There are only two modes read-only and read-write """
|
|
||||||
def fget(self):
|
|
||||||
mode, x = "-", "-"
|
|
||||||
if self.is_dir: mode, x = "d", "x"
|
|
||||||
if self.is_readonly: mode += "r-"+x+"r-"+x+"r-"+x
|
|
||||||
else: mode += "rw"+x+"rw"+x+"rw"+x
|
|
||||||
return mode
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def name_in_color():
|
|
||||||
doc=""" The name in ANSI text. Directories are blue, ebooks are green """
|
|
||||||
def fget(self):
|
|
||||||
cname = self.name
|
|
||||||
blue, green, normal = "", "", ""
|
|
||||||
if _term: blue, green, normal = _term.BLUE, _term.GREEN, _term.NORMAL
|
|
||||||
if self.is_dir: cname = blue + self.name + normal
|
|
||||||
else:
|
|
||||||
ext = self.name[self.name.rfind("."):]
|
|
||||||
if ext in (".pdf", ".rtf", ".lrf", ".lrx", ".txt"): cname = green + self.name + normal
|
|
||||||
return cname
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def human_readable_size():
|
|
||||||
doc=""" File size in human readable form """
|
|
||||||
def fget(self):
|
|
||||||
if self.size < 1024: divisor, suffix = 1, ""
|
|
||||||
elif self.size < 1024*1024: divisor, suffix = 1024., "M"
|
|
||||||
elif self.size < 1024*1024*1024: divisor, suffix = 1024*1024, "G"
|
|
||||||
size = str(self.size/divisor)
|
|
||||||
if size.find(".") > -1: size = size[:size.find(".")+2]
|
|
||||||
return size + suffix
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def modification_time():
|
|
||||||
doc=""" Last modified time in the Linux ls -l format """
|
|
||||||
def fget(self):
|
|
||||||
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.wtime))
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def creation_time():
|
|
||||||
doc=""" Last modified time in the Linux ls -l format """
|
|
||||||
def fget(self):
|
|
||||||
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.ctime))
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceDescriptor:
|
|
||||||
def __init__(self, vendor_id, product_id, interface_id) :
|
|
||||||
self.vendor_id = vendor_id
|
|
||||||
self.product_id = product_id
|
|
||||||
self.interface_id = interface_id
|
|
||||||
|
|
||||||
def getDevice(self) :
|
|
||||||
"""
|
|
||||||
Return the device corresponding to the device descriptor if it is
|
|
||||||
available on a USB bus. Otherwise, return None. Note that the
|
|
||||||
returned device has yet to be claimed or opened.
|
|
||||||
"""
|
|
||||||
buses = usb.busses()
|
|
||||||
for bus in buses :
|
|
||||||
for device in bus.devices :
|
|
||||||
if device.idVendor == self.vendor_id :
|
|
||||||
if device.idProduct == self.product_id :
|
|
||||||
return device
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PRS500Device(object):
|
|
||||||
SONY_VENDOR_ID = 0x054c
|
|
||||||
PRS500_PRODUCT_ID = 0x029b
|
|
||||||
PRS500_INTERFACE_ID = 0
|
|
||||||
PRS500_BULK_IN_EP = 0x81
|
|
||||||
PRS500_BULK_OUT_EP = 0x02
|
|
||||||
|
|
||||||
def __init__(self) :
|
|
||||||
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
|
||||||
PRS500Device.PRS500_PRODUCT_ID,
|
|
||||||
PRS500Device.PRS500_INTERFACE_ID)
|
|
||||||
self.device = self.device_descriptor.getDevice()
|
|
||||||
self.handle = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _validate_response(cls, res, type=0x00, number=0x00):
|
|
||||||
if type != res.type or number != res.rnumber:
|
|
||||||
raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
|
|
||||||
"\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
|
|
||||||
|
|
||||||
def open(self) :
|
|
||||||
self.device = self.device_descriptor.getDevice()
|
|
||||||
if not self.device:
|
|
||||||
print >> sys.stderr, "Unable to find Sony Reader. Is it connected?"
|
|
||||||
sys.exit(1)
|
|
||||||
self.handle = self.device.open()
|
|
||||||
if sys.platform == 'darwin' :
|
|
||||||
# XXX : For some reason, Mac OS X doesn't set the
|
|
||||||
# configuration automatically like Linux does.
|
|
||||||
self.handle.setConfiguration(1)
|
|
||||||
self.handle.claimInterface(self.device_descriptor.interface_id)
|
|
||||||
self.handle.reset()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.handle.releaseInterface()
|
|
||||||
self.handle, self.device = None, None
|
|
||||||
|
|
||||||
def _send_command(self, command, response_type=Response, timeout=100):
|
|
||||||
"""
|
|
||||||
Send command to device and return its response.
|
|
||||||
|
|
||||||
command -- an object of type Command or one of its derived classes
|
|
||||||
response_type -- an object of type 'type'. The return packet from the device is returned as an object of type response_type.
|
|
||||||
timeout -- the time to wait for a response from the device, in milliseconds
|
|
||||||
"""
|
|
||||||
if LOG_PACKETS: print "Command\n%s\n--\n"%command
|
|
||||||
bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
|
|
||||||
if bytes_sent != len(command):
|
|
||||||
raise ControlError(desc="Could not send control request to device\n" + str(query.query))
|
|
||||||
response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
|
|
||||||
if LOG_PACKETS: print "Response\n%s\n--\n"%response
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=100):
|
|
||||||
""" Wrapper around _send_command that checks if the response's rnumber == cnumber or command.number if cnumber==None """
|
|
||||||
if cnumber == None: cnumber = command.number
|
|
||||||
res = self._send_command(command, response_type=response_type, timeout=timeout)
|
|
||||||
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _bulk_read(self, data_type=Answer, size=4096):
|
|
||||||
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
|
||||||
if LOG_PACKETS: print "Answer\n%s\n--\n"%data
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _read_single_bulk_packet(self, command_number=0x00, data_type=Answer, size=4096):
|
|
||||||
data = self._bulk_read(data_type=data_type, size=size)
|
|
||||||
self._send_validated_command(AcknowledgeBulkRead(data.id), cnumber=command_number)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _test_bulk_reads(self):
|
|
||||||
self._send_validated_command( ShortCommand(number=0x00, type=0x01, command=0x00) )
|
|
||||||
self._read_single_bulk_packet(command_number=0x00, size=24)
|
|
||||||
|
|
||||||
def _start_session(self):
|
|
||||||
self.handle.reset()
|
|
||||||
self._test_bulk_reads()
|
|
||||||
self._send_validated_command( ShortCommand(number=0x0107, command=0x028000, type=0x01) ) # TODO: Figure out the meaning of this command
|
|
||||||
self._test_bulk_reads()
|
|
||||||
self._send_validated_command( ShortCommand(number=0x0106, type=0x01, command=0x312d) ) # TODO: Figure out the meaning of this command
|
|
||||||
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x01) )
|
|
||||||
|
|
||||||
def _end_session(self):
|
|
||||||
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x00) )
|
|
||||||
|
|
||||||
def _run_session(self, *args):
|
|
||||||
self._start_session()
|
|
||||||
res = None
|
|
||||||
try:
|
|
||||||
res = args[0](args[1:])
|
|
||||||
finally:
|
|
||||||
self._end_session()
|
|
||||||
pass
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_path_properties(self, path):
|
|
||||||
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
|
||||||
data = self._read_single_bulk_packet(size=0x28, data_type=PathAnswer, command_number=PathQuery.PROPERTIES)
|
|
||||||
if res.path_not_found : raise PathError(path[:-1] + " does not exist on device")
|
|
||||||
if res.is_invalid : raise PathError(path[:-1] + " is not a valid path")
|
|
||||||
if res.is_unmounted : raise PathError(path[:-1] + " is not mounted")
|
|
||||||
return (res, data)
|
|
||||||
|
|
||||||
def _list(self, args):
|
|
||||||
path = args[0]
|
|
||||||
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
|
||||||
files = []
|
|
||||||
res, data = self._get_path_properties(path)
|
|
||||||
if res.is_file:
|
|
||||||
path = path[:-1]
|
|
||||||
res, data = self._get_path_properties(path)
|
|
||||||
files = [ (path, data) ]
|
|
||||||
else:
|
|
||||||
self._send_validated_command(PathQuery(path, number=PathQuery.ID), response_type=ListResponse)
|
|
||||||
id = self._read_single_bulk_packet(size=0x14, data_type=IdAnswer, command_number=PathQuery.ID).id
|
|
||||||
next = ShortCommand.list_command(id=id)
|
|
||||||
cnumber = next.number
|
|
||||||
items = []
|
|
||||||
while True:
|
|
||||||
res = self._send_validated_command(next, response_type=ListResponse)
|
|
||||||
size = res.data[2] + 16
|
|
||||||
data = self._read_single_bulk_packet(size=size, data_type=ListAnswer, command_number=cnumber)
|
|
||||||
# path_not_found seems to happen if the usb server doesn't have the permissions to access the directory
|
|
||||||
if res.is_eol or res.path_not_found: break
|
|
||||||
items.append(data.name)
|
|
||||||
for item in items:
|
|
||||||
ipath = path + item
|
|
||||||
res, data = self._get_path_properties(ipath)
|
|
||||||
files.append( (ipath, data) )
|
|
||||||
files.sort()
|
|
||||||
return files
|
|
||||||
|
|
||||||
def list(self, path, recurse=False):
|
|
||||||
files = self._run_session(self._list, path)
|
|
||||||
files = [ File(file) for file in files ]
|
|
||||||
dirs = [(path, files)]
|
|
||||||
for file in files:
|
|
||||||
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
|
||||||
dirs[len(dirs):] = self.list(file.path, recurse=True)
|
|
||||||
return dirs
|
|
||||||
|
|
||||||
def ls(self, path, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
|
|
||||||
def col_split(l, cols): # split list l into columns
|
|
||||||
rows = len(l) / cols
|
|
||||||
if len(l) % cols:
|
|
||||||
rows += 1
|
|
||||||
m = []
|
|
||||||
for i in range(rows):
|
|
||||||
m.append(l[i::rows])
|
|
||||||
return m
|
|
||||||
|
|
||||||
def row_widths(table): # Calculate widths for each column in the row-wise table
|
|
||||||
tcols = len(table[0])
|
|
||||||
rowwidths = [ 0 for i in range(tcols) ]
|
|
||||||
for row in table:
|
|
||||||
c = 0
|
|
||||||
for item in row:
|
|
||||||
rowwidths[c] = len(item) if len(item) > rowwidths[c] else rowwidths[c]
|
|
||||||
c += 1
|
|
||||||
return rowwidths
|
|
||||||
|
|
||||||
output = StringIO.StringIO()
|
|
||||||
if path.endswith("/"): path = path[:-1]
|
|
||||||
dirs = self.list(path, recurse)
|
|
||||||
for dir in dirs:
|
|
||||||
if recurse: print >>output, dir[0] + ":"
|
|
||||||
lsoutput, lscoloutput = [], []
|
|
||||||
files = dir[1]
|
|
||||||
maxlen = 0
|
|
||||||
if ll: # Calculate column width for size column
|
|
||||||
for file in files:
|
|
||||||
size = len(str(file.size))
|
|
||||||
if human_readable_size: size = len(file.human_readable_size)
|
|
||||||
if size > maxlen: maxlen = size
|
|
||||||
for file in files:
|
|
||||||
name = file.name
|
|
||||||
lsoutput.append(name)
|
|
||||||
if color: name = file.name_in_color
|
|
||||||
lscoloutput.append(name)
|
|
||||||
if ll:
|
|
||||||
size = str(file.size)
|
|
||||||
if human_readable_size: size = file.human_readable_size
|
|
||||||
print >>output, file.mode_string, ("%"+str(maxlen)+"s")%size, file.modification_time, name
|
|
||||||
if not ll and len(lsoutput) > 0:
|
|
||||||
trytable = []
|
|
||||||
for colwidth in range(MINIMUM_COL_WIDTH, cols):
|
|
||||||
trycols = int(cols/colwidth)
|
|
||||||
trytable = col_split(lsoutput, trycols)
|
|
||||||
works = True
|
|
||||||
for row in trytable:
|
|
||||||
row_break = False
|
|
||||||
for item in row:
|
|
||||||
if len(item) > colwidth - 1:
|
|
||||||
works, row_break = False, True
|
|
||||||
break
|
|
||||||
if row_break: break
|
|
||||||
if works: break
|
|
||||||
rowwidths = row_widths(trytable)
|
|
||||||
trytablecol = col_split(lscoloutput, len(trytable[0]))
|
|
||||||
for r in range(len(trytable)):
|
|
||||||
for c in range(len(trytable[r])):
|
|
||||||
padding = rowwidths[c] - len(trytable[r][c])
|
|
||||||
print >>output, trytablecol[r][c], "".ljust(padding),
|
|
||||||
print >>output
|
|
||||||
print >>output
|
|
||||||
listing = output.getvalue().rstrip()+ "\n"
|
|
||||||
output.close()
|
|
||||||
return listing
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
if _term : cols = _term.COLS
|
|
||||||
else: cols = 70
|
|
||||||
|
|
||||||
parser = OptionParser(usage="usage: %prog command [options] args\n\ncommand is one of: ls, get, put or rm\n\n"+
|
|
||||||
"For help on a particular command: %prog command")
|
|
||||||
parser.add_option("--log-packets", help="print out packet stream to stdout", dest="log_packets", action="store_true", default=False)
|
|
||||||
parser.remove_option("-h")
|
|
||||||
parser.disable_interspersed_args() # Allow unrecognized options
|
|
||||||
options, args = parser.parse_args()
|
|
||||||
global LOG_PACKETS
|
|
||||||
LOG_PACKETS = options.log_packets
|
|
||||||
if len(args) < 1:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
command = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
dev = PRS500Device()
|
|
||||||
if command == "ls":
|
|
||||||
parser = OptionParser(usage="usage: %prog ls [options] path\n\npath must begin with /,a:/ or b:/")
|
|
||||||
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
|
||||||
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time unless other times are selected)", dest="ll", action="store_true", default=False)
|
|
||||||
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
|
||||||
parser.remove_option("-h")
|
|
||||||
parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False)
|
|
||||||
options, args = parser.parse_args(args)
|
|
||||||
if len(args) < 1:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
dev.open()
|
|
||||||
try:
|
|
||||||
print dev.ls(args[0], color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
|
||||||
except PathError, e:
|
|
||||||
print >> sys.stderr, e
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
|
||||||
dev.close()
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
_term = TerminalController()
|
|
||||||
main(sys.argv)
|
|
27
decoding
27
decoding
@ -1,27 +0,0 @@
|
|||||||
Control packets (see pg 199 in usb1.1 spec)
|
|
||||||
|
|
||||||
00 01 02 03 04 05 06 07
|
|
||||||
|
|
||||||
00 - Request Type
|
|
||||||
01 - Request
|
|
||||||
02,03 - Value
|
|
||||||
04,05 - Index/Offset
|
|
||||||
06,07 - Data length
|
|
||||||
|
|
||||||
|
|
||||||
Request Type
|
|
||||||
80 - device to host; type standard; recipient device
|
|
||||||
c0 - device to host; type vendor ; recipient device
|
|
||||||
40 - host to device; type vendor ; recipient device
|
|
||||||
|
|
||||||
c0, 40 are used for sony communication
|
|
||||||
|
|
||||||
|
|
||||||
Request
|
|
||||||
06 - GET_DESCRIPTOR
|
|
||||||
80 - Sony proprietary. goes with 40 on first bit
|
|
||||||
81 - Sony proprietary. goes with c0 on first bit
|
|
||||||
|
|
||||||
|
|
||||||
Data length
|
|
||||||
Only the 6th byte seems to be used. The value of the 6th byte converted to decimal is the number of bytes to be read/written.
|
|
50
epydoc-pdf.conf
Normal file
50
epydoc-pdf.conf
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
[epydoc] # Epydoc section marker (required by ConfigParser)
|
||||||
|
|
||||||
|
# Information about the project.
|
||||||
|
name: libprs500
|
||||||
|
#url: http://
|
||||||
|
|
||||||
|
# The list of modules to document. Modules can be named using
|
||||||
|
# dotted names, module filenames, or package directory names.
|
||||||
|
# This option may be repeated.
|
||||||
|
modules: libprs500, scripts/prs500.py, usb, struct
|
||||||
|
|
||||||
|
output: pdf
|
||||||
|
target: docs/pdf
|
||||||
|
|
||||||
|
frames: no
|
||||||
|
|
||||||
|
# graph
|
||||||
|
# The list of graph types that should be automatically included
|
||||||
|
# in the output. Graphs are generated using the Graphviz "dot"
|
||||||
|
# executable. Graph types include: "classtree", "callgraph",
|
||||||
|
# "umlclass". Use "all" to include all graph types
|
||||||
|
graph: classtree
|
||||||
|
|
||||||
|
# css
|
||||||
|
# The CSS stylesheet for HTML output. Can be the name of a builtin
|
||||||
|
# stylesheet, or the name of a file.
|
||||||
|
css: white
|
||||||
|
|
||||||
|
# link
|
||||||
|
# HTML code for the project link in the navigation bar. If left
|
||||||
|
# unspecified, the project link will be generated based on the
|
||||||
|
# project's name and URL.
|
||||||
|
#link: <a href="somewhere">My Cool Project</a>
|
||||||
|
|
||||||
|
# top
|
||||||
|
# The "top" page for the documentation. Can be a URL, the name
|
||||||
|
# of a module or class, or one of the special names "trees.html",
|
||||||
|
# "indices.html", or "help.html"
|
||||||
|
top: libprs500
|
||||||
|
|
||||||
|
# verbosity
|
||||||
|
# An integer indicating how verbose epydoc should be. The default
|
||||||
|
# value is 0; negative values will supress warnings and errors;
|
||||||
|
# positive values will give more verbose output.
|
||||||
|
#verbosity: 0
|
||||||
|
|
||||||
|
# separate-classes
|
||||||
|
# Whether each class should be listed in its own section when
|
||||||
|
# generating LaTeX or PDF output.
|
||||||
|
#separate-classes: no
|
48
epydoc.conf
48
epydoc.conf
@ -1,19 +1,51 @@
|
|||||||
[epydoc] # Epydoc section marker (required by ConfigParser)
|
[epydoc] # Epydoc section marker (required by ConfigParser)
|
||||||
|
|
||||||
# Information about the project.
|
# Information about the project.
|
||||||
name: My Cool Project
|
name: libprs500
|
||||||
url: http://cool.project/
|
#url: http://
|
||||||
|
|
||||||
# The list of modules to document. Modules can be named using
|
# The list of modules to document. Modules can be named using
|
||||||
# dotted names, module filenames, or package directory names.
|
# dotted names, module filenames, or package directory names.
|
||||||
# This option may be repeated.
|
# This option may be repeated.
|
||||||
modules: usb, struct
|
modules: libprs500, scripts/prs500.py, usb, struct
|
||||||
modules: prstypes.py, communicate.py, errors.py
|
|
||||||
|
|
||||||
# Write html output to the directory "apidocs"
|
# Write html output to the directory "apidocs"
|
||||||
output: html
|
output: html
|
||||||
target: apidocs/
|
target: docs/html
|
||||||
|
|
||||||
# Include all automatically generated graphs. These graphs are
|
frames: no
|
||||||
# generated using Graphviz dot.
|
|
||||||
graph: all
|
# graph
|
||||||
|
# The list of graph types that should be automatically included
|
||||||
|
# in the output. Graphs are generated using the Graphviz "dot"
|
||||||
|
# executable. Graph types include: "classtree", "callgraph",
|
||||||
|
# "umlclass". Use "all" to include all graph types
|
||||||
|
graph: classtree
|
||||||
|
|
||||||
|
# css
|
||||||
|
# The CSS stylesheet for HTML output. Can be the name of a builtin
|
||||||
|
# stylesheet, or the name of a file.
|
||||||
|
css: white
|
||||||
|
|
||||||
|
# link
|
||||||
|
# HTML code for the project link in the navigation bar. If left
|
||||||
|
# unspecified, the project link will be generated based on the
|
||||||
|
# project's name and URL.
|
||||||
|
#link: <a href="somewhere">My Cool Project</a>
|
||||||
|
|
||||||
|
# top
|
||||||
|
# The "top" page for the documentation. Can be a URL, the name
|
||||||
|
# of a module or class, or one of the special names "trees.html",
|
||||||
|
# "indices.html", or "help.html"
|
||||||
|
top: libprs500
|
||||||
|
|
||||||
|
# verbosity
|
||||||
|
# An integer indicating how verbose epydoc should be. The default
|
||||||
|
# value is 0; negative values will supress warnings and errors;
|
||||||
|
# positive values will give more verbose output.
|
||||||
|
#verbosity: 0
|
||||||
|
|
||||||
|
# separate-classes
|
||||||
|
# Whether each class should be listed in its own section when
|
||||||
|
# generating LaTeX or PDF output.
|
||||||
|
#separate-classes: no
|
||||||
|
32
errors.py
32
errors.py
@ -1,32 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
from exceptions import Exception
|
|
||||||
|
|
||||||
class ProtocolError(Exception):
|
|
||||||
""" The base class for all exceptions in this package """
|
|
||||||
def __init__(self, msg):
|
|
||||||
Exception.__init__(self, msg)
|
|
||||||
|
|
||||||
class PacketError(ProtocolError):
|
|
||||||
""" Errors with creating/interpreting packets """
|
|
||||||
def __init__(self, msg):
|
|
||||||
ProtocolError.__init__(self, msg)
|
|
||||||
|
|
||||||
class PathError(ProtocolError):
|
|
||||||
def __init__(self, msg):
|
|
||||||
Exception.__init__(self, msg)
|
|
||||||
|
|
||||||
class ControlError(ProtocolError):
|
|
||||||
def __init__(self, query=None, response=None, desc=None):
|
|
||||||
self.query = query
|
|
||||||
self.response = response
|
|
||||||
Exception.__init__(self, desc)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.query and self.response:
|
|
||||||
return "Got unexpected response:\n" + \
|
|
||||||
"query:\n"+str(self.query.query)+"\n"+\
|
|
||||||
"expected:\n"+str(self.query.response)+"\n" +\
|
|
||||||
"actual:\n"+str(self.response)
|
|
||||||
if self.desc:
|
|
||||||
return self.desc
|
|
||||||
return "Unknown control error occurred"
|
|
25
libprs500/__init__.py
Normal file
25
libprs500/__init__.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
This package provides an interface to the SONY Reader PRS-500 over USB.
|
||||||
|
|
||||||
|
The public interface of libprs500 is in L{libprs500.communicate}. To use it
|
||||||
|
>>> from libprs500.communicate import PRS500Device
|
||||||
|
>>> dev = PRS500Device()
|
||||||
|
>>> dev.open()
|
||||||
|
>>> dev.get_device_information()
|
||||||
|
('Sony Reader', 'PRS-500/U', '1.0.00.21081', 'application/x-bbeb-book')
|
||||||
|
>>> dev.close()
|
||||||
|
|
||||||
|
There is also a script L{prs500} that provides a command-line interface to libprs500. See the script
|
||||||
|
for more usage examples.
|
||||||
|
|
||||||
|
The packet structure used by the SONY Reader USB protocol is defined in the module L{prstypes}. The communication logic
|
||||||
|
is defined in the module L{communicate}.
|
||||||
|
|
||||||
|
This package requires U{PyUSB<http://pyusb.berlios.de/>}. In order to use it as a non-root user on Linux, you should have
|
||||||
|
the following rule in C{/etc/udev/rules.d/90-local.rules} ::
|
||||||
|
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
||||||
|
You may have to adjust the GROUP and the location of the rules file to suit your distribution.
|
||||||
|
"""
|
||||||
|
VERSION = "0.1"
|
||||||
|
__docformat__ = "epytext"
|
||||||
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
366
libprs500/communicate.py
Executable file
366
libprs500/communicate.py
Executable file
@ -0,0 +1,366 @@
|
|||||||
|
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
||||||
|
## 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.,
|
||||||
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
### End point description for PRS-500 procductId=667
|
||||||
|
### Endpoint Descriptor:
|
||||||
|
### bLength 7
|
||||||
|
### bDescriptorType 5
|
||||||
|
### bEndpointAddress 0x81 EP 1 IN
|
||||||
|
### bmAttributes 2
|
||||||
|
### Transfer Type Bulk
|
||||||
|
### Synch Type None
|
||||||
|
### Usage Type Data
|
||||||
|
### wMaxPacketSize 0x0040 1x 64 bytes
|
||||||
|
### bInterval 0
|
||||||
|
### Endpoint Descriptor:
|
||||||
|
### bLength 7
|
||||||
|
### bDescriptorType 5
|
||||||
|
### bEndpointAddress 0x02 EP 2 OUT
|
||||||
|
### bmAttributes 2
|
||||||
|
### Transfer Type Bulk
|
||||||
|
### Synch Type None
|
||||||
|
### Usage Type Data
|
||||||
|
### wMaxPacketSize 0x0040 1x 64 bytes
|
||||||
|
### bInterval 0
|
||||||
|
###
|
||||||
|
###
|
||||||
|
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
||||||
|
### Has two configurations 1 is the USB charging config 2 is the self-powered config.
|
||||||
|
### I think config management is automatic. Endpoints are the same
|
||||||
|
"""
|
||||||
|
Contains the logic for communication with the device (a SONY PRS-500).
|
||||||
|
|
||||||
|
The public interface of class L{PRS500Device} defines the methods for performing various tasks.
|
||||||
|
"""
|
||||||
|
import usb, sys
|
||||||
|
from array import array
|
||||||
|
|
||||||
|
from prstypes import AcknowledgeBulkRead, Answer, Command, DeviceInfo, DirOpen, DirRead, DirClose, \
|
||||||
|
FileOpen, FileClose, FileRead, IdAnswer, ListAnswer, \
|
||||||
|
ListResponse, LongCommand, FileProperties, PathQuery, Response, \
|
||||||
|
ShortCommand, DeviceInfoQuery
|
||||||
|
from errors import *
|
||||||
|
|
||||||
|
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||||
|
|
||||||
|
class File(object):
|
||||||
|
""" Wrapper that allows easy access to all information about files/directories """
|
||||||
|
def __init__(self, file):
|
||||||
|
self.is_dir = file[1].is_dir #: True if self is a directory
|
||||||
|
self.is_readonly = file[1].is_readonly #: True if self is readonly
|
||||||
|
self.size = file[1].file_size #: Size in bytes of self
|
||||||
|
self.ctime = file[1].ctime #: Creation time of self as a epoch
|
||||||
|
self.wtime = file[1].wtime #: Creation time of self as an epoch
|
||||||
|
path = file[0]
|
||||||
|
if path.endswith("/"): path = path[:-1]
|
||||||
|
self.path = path #: Path to self
|
||||||
|
self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
""" Return path to self """
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDescriptor:
|
||||||
|
"""
|
||||||
|
Describes a USB device.
|
||||||
|
|
||||||
|
A description is composed of the Vendor Id, Product Id and Interface Id.
|
||||||
|
See the U{USB spec<http://www.usb.org/developers/docs/usb_20_05122006.zip>}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vendor_id, product_id, interface_id) :
|
||||||
|
self.vendor_id = vendor_id
|
||||||
|
self.product_id = product_id
|
||||||
|
self.interface_id = interface_id
|
||||||
|
|
||||||
|
def getDevice(self) :
|
||||||
|
"""
|
||||||
|
Return the device corresponding to the device descriptor if it is
|
||||||
|
available on a USB bus. Otherwise, return None. Note that the
|
||||||
|
returned device has yet to be claimed or opened.
|
||||||
|
"""
|
||||||
|
buses = usb.busses()
|
||||||
|
for bus in buses :
|
||||||
|
for device in bus.devices :
|
||||||
|
if device.idVendor == self.vendor_id :
|
||||||
|
if device.idProduct == self.product_id :
|
||||||
|
return device
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class PRS500Device(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Contains the logic for performing various tasks on the reader.
|
||||||
|
|
||||||
|
The implemented tasks are:
|
||||||
|
0. Getting information about the device
|
||||||
|
1. Getting a file from the device
|
||||||
|
2. Listing of directories. See the C{list} method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SONY_VENDOR_ID = 0x054c #: SONY Vendor Id
|
||||||
|
PRS500_PRODUCT_ID = 0x029b #: Product Id for the PRS-500
|
||||||
|
PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device
|
||||||
|
PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads
|
||||||
|
PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes
|
||||||
|
|
||||||
|
def __init__(self, log_packets=False) :
|
||||||
|
""" @param log_packets: If true the packet stream to/from the device is logged """
|
||||||
|
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
||||||
|
PRS500Device.PRS500_PRODUCT_ID,
|
||||||
|
PRS500Device.PRS500_INTERFACE_ID)
|
||||||
|
self.device = self.device_descriptor.getDevice()
|
||||||
|
self.handle = None
|
||||||
|
self._log_packets = log_packets
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _validate_response(cls, res, type=0x00, number=0x00):
|
||||||
|
""" Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
|
||||||
|
if type != res.type or number != res.rnumber:
|
||||||
|
raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
|
||||||
|
"\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
|
||||||
|
|
||||||
|
def open(self) :
|
||||||
|
"""
|
||||||
|
Claim an interface on the device for communication. Requires write privileges to the device file.
|
||||||
|
|
||||||
|
@todo: Check this on Mac OSX
|
||||||
|
"""
|
||||||
|
self.device = self.device_descriptor.getDevice()
|
||||||
|
if not self.device:
|
||||||
|
print >> sys.stderr, "Unable to find Sony Reader. Is it connected?"
|
||||||
|
sys.exit(1)
|
||||||
|
self.handle = self.device.open()
|
||||||
|
if sys.platform == 'darwin' :
|
||||||
|
# XXX : For some reason, Mac OS X doesn't set the
|
||||||
|
# configuration automatically like Linux does.
|
||||||
|
self.handle.setConfiguration(1) # TODO: Check on Mac OSX
|
||||||
|
self.handle.claimInterface(self.device_descriptor.interface_id)
|
||||||
|
self.handle.reset()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
""" Release device interface """
|
||||||
|
self.handle.releaseInterface()
|
||||||
|
self.handle, self.device = None, None
|
||||||
|
|
||||||
|
def _send_command(self, command, response_type=Response, timeout=100):
|
||||||
|
"""
|
||||||
|
Send L{command<Command>} to device and return its L{response<Response>}.
|
||||||
|
|
||||||
|
@param command: an object of type Command or one of its derived classes
|
||||||
|
@param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type.
|
||||||
|
@param timeout: the time to wait for a response from the device, in milliseconds. If there is no response, a L{usb.USBError} is raised.
|
||||||
|
"""
|
||||||
|
if self._log_packets: print "Command\n%s\n--\n"%command
|
||||||
|
bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
|
||||||
|
if bytes_sent != len(command):
|
||||||
|
raise ControlError(desc="Could not send control request to device\n" + str(query.query))
|
||||||
|
response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
|
||||||
|
if self._log_packets: print "Response\n%s\n--\n"%response
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=100):
|
||||||
|
"""
|
||||||
|
Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or command.number if cnumber==None}. Also check that
|
||||||
|
C{Response.type == Command.type}.
|
||||||
|
"""
|
||||||
|
if cnumber == None: cnumber = command.number
|
||||||
|
res = self._send_command(command, response_type=response_type, timeout=timeout)
|
||||||
|
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _bulk_read_packet(self, data_type=Answer, size=4096):
|
||||||
|
"""
|
||||||
|
Read in a data packet via a Bulk Read.
|
||||||
|
|
||||||
|
@param data_type: an object of type type. The data packet is returned as an object of type C{data_type}.
|
||||||
|
@param size: the expected size of the data packet.
|
||||||
|
"""
|
||||||
|
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
||||||
|
if self._log_packets: print "Answer\n%s\n--\n"%data
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
|
||||||
|
""" Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size} """
|
||||||
|
bytes_left = bytes
|
||||||
|
packets = []
|
||||||
|
while bytes_left > 0:
|
||||||
|
if packet_size > bytes_left: packet_size = bytes_left
|
||||||
|
packet = self._bulk_read_packet(data_type=data_type, size=packet_size)
|
||||||
|
bytes_left -= len(packet)
|
||||||
|
packets.append(packet)
|
||||||
|
self._send_validated_command(AcknowledgeBulkRead(packets[0].id), cnumber=command_number)
|
||||||
|
return packets
|
||||||
|
|
||||||
|
def _test_bulk_reads(self):
|
||||||
|
""" Carries out a test of bulk reading as part of session initialization. """
|
||||||
|
self._send_validated_command( ShortCommand(number=0x00, type=0x01, command=0x00) )
|
||||||
|
self._bulk_read(24, command_number=0x00)
|
||||||
|
|
||||||
|
def _start_session(self):
|
||||||
|
"""
|
||||||
|
Send the initialization sequence to the device. See the code for details.
|
||||||
|
This method should be called before any real work is done. Though most things seem to work without it.
|
||||||
|
"""
|
||||||
|
self.handle.reset()
|
||||||
|
self._test_bulk_reads()
|
||||||
|
self._send_validated_command( ShortCommand(number=0x0107, command=0x028000, type=0x01) ) # TODO: Figure out the meaning of this command
|
||||||
|
self._test_bulk_reads()
|
||||||
|
self._send_validated_command( ShortCommand(number=0x0106, type=0x01, command=0x312d) ) # TODO: Figure out the meaning of this command
|
||||||
|
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x01) )
|
||||||
|
|
||||||
|
def _end_session(self):
|
||||||
|
""" Send the end session command to the device. Causes the device to change status from "Do not disconnect" to "USB Connected" """
|
||||||
|
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x00) )
|
||||||
|
|
||||||
|
def _run_session(self, *args):
|
||||||
|
"""
|
||||||
|
Wrapper that automatically calls L{_start_session} and L{_end_session}.
|
||||||
|
|
||||||
|
@param args: An array whose first element is the method to call and whose remaining arguments are passed to that mathos as an array.
|
||||||
|
"""
|
||||||
|
self._start_session()
|
||||||
|
res = None
|
||||||
|
try:
|
||||||
|
res = args[0](args[1:])
|
||||||
|
except ArgumentError, e:
|
||||||
|
self._end_session()
|
||||||
|
raise e
|
||||||
|
self._end_session()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_device_information(self, args):
|
||||||
|
""" Ask device for device information. See L{DeviceInfoQuery}. """
|
||||||
|
size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16
|
||||||
|
data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0]
|
||||||
|
return (data.device_name, data.device_version, data.software_version, data.mime_type)
|
||||||
|
|
||||||
|
def get_device_information(self):
|
||||||
|
""" Return (device name, device version, software version on device, mime type). See L{_get_device_information} """
|
||||||
|
return self._run_session(self._get_device_information)
|
||||||
|
|
||||||
|
def _get_path_properties(self, path):
|
||||||
|
""" Send command asking device for properties of C{path}. Return (L{Response}, L{Answer}). """
|
||||||
|
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
||||||
|
data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0]
|
||||||
|
if path.endswith("/"): path = path[:-1]
|
||||||
|
if res.path_not_found : raise PathError(path + " does not exist on device")
|
||||||
|
if res.is_invalid : raise PathError(path + " is not a valid path")
|
||||||
|
if res.is_unmounted : raise PathError(path + " is not mounted")
|
||||||
|
return (res, data)
|
||||||
|
|
||||||
|
def get_file(self, path, outfile):
|
||||||
|
"""
|
||||||
|
Read the file at path on the device and write it to outfile. For the logic see L{_get_file}.
|
||||||
|
|
||||||
|
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
||||||
|
"""
|
||||||
|
self._run_session(self._get_file, path, outfile)
|
||||||
|
|
||||||
|
def _get_file(self, args):
|
||||||
|
"""
|
||||||
|
Fetch a file from the device and write it to an output stream.
|
||||||
|
|
||||||
|
The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen},
|
||||||
|
L{FileRead} and L{FileClose} for details on the command packets used.
|
||||||
|
|
||||||
|
@param args: C{path, outfile = arg[0], arg[1]}
|
||||||
|
"""
|
||||||
|
path, outfile = args[0], args[1]
|
||||||
|
if path.endswith("/"): path = path[:-1] # We only copy files
|
||||||
|
res, data = self._get_path_properties(path)
|
||||||
|
if data.is_dir: raise PathError("Cannot read as " + path + " is a directory")
|
||||||
|
bytes = data.file_size
|
||||||
|
self._send_validated_command(FileOpen(path))
|
||||||
|
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
|
||||||
|
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
||||||
|
while bytes_left > 0:
|
||||||
|
if chunk_size > bytes_left: chunk_size = bytes_left
|
||||||
|
res = self._send_validated_command(FileRead(id, pos, chunk_size))
|
||||||
|
packets = self._bulk_read(chunk_size+16, command_number=FileRead.NUMBER, packet_size=4096)
|
||||||
|
try:
|
||||||
|
array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream
|
||||||
|
for i in range(1, len(packets)):
|
||||||
|
array('B', packets[i]).tofile(outfile)
|
||||||
|
except IOError, e:
|
||||||
|
self._send_validated_command(FileClose(id))
|
||||||
|
raise ArgumentError("File get operation failed. Could not write to local location: " + str(e))
|
||||||
|
bytes_left -= chunk_size
|
||||||
|
pos += chunk_size
|
||||||
|
self._send_validated_command(FileClose(id))
|
||||||
|
|
||||||
|
|
||||||
|
def _list(self, args):
|
||||||
|
"""
|
||||||
|
Ask the device to list a path. See the code for details. See L{DirOpen},
|
||||||
|
L{DirRead} and L{DirClose} for details on the command packets used.
|
||||||
|
|
||||||
|
@param args: C{path=args[0]}
|
||||||
|
@return: A list of tuples. The first element of each tuple is a string, the path. The second is a L{FileProperties}.
|
||||||
|
If the path points to a file, the list will have length 1.
|
||||||
|
"""
|
||||||
|
path = args[0]
|
||||||
|
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
||||||
|
files = []
|
||||||
|
res, data = self._get_path_properties(path)
|
||||||
|
if res.is_file:
|
||||||
|
path = path[:-1]
|
||||||
|
res, data = self._get_path_properties(path)
|
||||||
|
files = [ (path, data) ]
|
||||||
|
else:
|
||||||
|
# Get query ID used to ask for next element in list
|
||||||
|
self._send_validated_command(DirOpen(path), response_type=ListResponse)
|
||||||
|
id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id
|
||||||
|
# Create command asking for next element in list
|
||||||
|
next = DirRead(id)
|
||||||
|
items = []
|
||||||
|
while True:
|
||||||
|
res = self._send_validated_command(next, response_type=ListResponse)
|
||||||
|
size = res.data[2] + 16
|
||||||
|
data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0]
|
||||||
|
# path_not_found seems to happen if the usb server doesn't have the permissions to access the directory
|
||||||
|
if res.is_eol or res.path_not_found: break
|
||||||
|
items.append(data.name)
|
||||||
|
self._send_validated_command(DirClose(id))
|
||||||
|
for item in items:
|
||||||
|
ipath = path + item
|
||||||
|
res, data = self._get_path_properties(ipath)
|
||||||
|
files.append( (ipath, data) )
|
||||||
|
files.sort()
|
||||||
|
return files
|
||||||
|
|
||||||
|
def list(self, path, recurse=False):
|
||||||
|
"""
|
||||||
|
Return a listing of path.
|
||||||
|
|
||||||
|
See L{_list} for the communication logic.
|
||||||
|
|
||||||
|
@type path: string
|
||||||
|
@param path: The path to list
|
||||||
|
@type recurse: boolean
|
||||||
|
@param recurse: If true do a recursive listing
|
||||||
|
@return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files<File>}.
|
||||||
|
The path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive
|
||||||
|
list, then the first element will be (C{path}, children), the next will be (child, its children) and so on.
|
||||||
|
"""
|
||||||
|
files = self._run_session(self._list, path)
|
||||||
|
files = [ File(file) for file in files ]
|
||||||
|
dirs = [(path, files)]
|
||||||
|
for file in files:
|
||||||
|
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
||||||
|
dirs[len(dirs):] = self.list(file.path, recurse=True)
|
||||||
|
return dirs
|
51
libprs500/errors.py
Normal file
51
libprs500/errors.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
||||||
|
## 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.,
|
||||||
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
"""
|
||||||
|
Defines the errors that libprs500 generates.
|
||||||
|
|
||||||
|
G{classtree ProtocolError}
|
||||||
|
"""
|
||||||
|
from exceptions import Exception
|
||||||
|
|
||||||
|
class ProtocolError(Exception):
|
||||||
|
""" The base class for all exceptions in this package """
|
||||||
|
def __init__(self, msg):
|
||||||
|
Exception.__init__(self, msg)
|
||||||
|
|
||||||
|
class PacketError(ProtocolError):
|
||||||
|
""" Errors with creating/interpreting packets """
|
||||||
|
|
||||||
|
class ArgumentError(ProtocolError):
|
||||||
|
""" Errors caused by invalid arguments to a public interface function """
|
||||||
|
|
||||||
|
class PathError(ArgumentError):
|
||||||
|
""" When a user supplies an incorrect/invalid path """
|
||||||
|
|
||||||
|
class ControlError(ProtocolError):
|
||||||
|
""" Errors in Command/Response pairs while communicating with the device """
|
||||||
|
def __init__(self, query=None, response=None, desc=None):
|
||||||
|
self.query = query
|
||||||
|
self.response = response
|
||||||
|
Exception.__init__(self, desc)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.query and self.response:
|
||||||
|
return "Got unexpected response:\n" + \
|
||||||
|
"query:\n"+str(self.query.query)+"\n"+\
|
||||||
|
"expected:\n"+str(self.query.response)+"\n" +\
|
||||||
|
"actual:\n"+str(self.response)
|
||||||
|
if self.desc:
|
||||||
|
return self.desc
|
||||||
|
return "Unknown control error occurred"
|
840
libprs500/prstypes.py
Executable file
840
libprs500/prstypes.py
Executable file
@ -0,0 +1,840 @@
|
|||||||
|
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
||||||
|
## 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.,
|
||||||
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Defines the structure of packets that are sent to/received from the device.
|
||||||
|
|
||||||
|
Packet structure is defined using classes and inheritance. Each class is a view that imposes
|
||||||
|
structure on the underlying data buffer. The data buffer is encoded in little-endian format, but you don't
|
||||||
|
have to worry about that if you are using the classes. The classes have instance variables with getter/setter functions defined
|
||||||
|
to take care of the encoding/decoding. The classes are intended to mimic C structs.
|
||||||
|
|
||||||
|
There are three kinds of packets. L{Commands<Command>}, L{Responses<Response>}, and L{Answers<Answer>}.
|
||||||
|
C{Commands} are sent to the device on the control bus, C{Responses} are received from the device,
|
||||||
|
also on the control bus. C{Answers} and their sub-classes represent data packets sent to/received from
|
||||||
|
the device via bulk transfers.
|
||||||
|
|
||||||
|
Commands are organized as follows: G{classtree Command}
|
||||||
|
|
||||||
|
You will typically only use sub-classes of Command.
|
||||||
|
|
||||||
|
Responses are organized as follows: G{classtree Response}
|
||||||
|
|
||||||
|
Responses inherit Command as they share header structure.
|
||||||
|
|
||||||
|
Answers are organized as follows: G{classtree Answer}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
from errors import PacketError
|
||||||
|
|
||||||
|
BYTE = "<B" #: Unsigned char little endian encoded in 1 byte
|
||||||
|
WORD = "<H" #: Unsigned short little endian encoded in 2 bytes
|
||||||
|
DWORD = "<I" #: Unsigned integer little endian encoded in 4 bytes
|
||||||
|
DDWORD = "<Q" #: Unsigned long long little endian encoded in 8 bytes
|
||||||
|
|
||||||
|
|
||||||
|
class TransferBuffer(list):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents raw (unstructured) data packets sent over the usb bus.
|
||||||
|
|
||||||
|
C{TransferBuffer} is a wrapper around the tuples used by L{PyUSB<usb>} for communication.
|
||||||
|
It has convenience methods to read and write data from the underlying buffer. See
|
||||||
|
L{TransferBuffer.pack} and L{TransferBuffer.unpack}.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, packet):
|
||||||
|
"""
|
||||||
|
Create a L{TransferBuffer} from C{packet} or an empty buffer.
|
||||||
|
|
||||||
|
@type packet: integer or listable object
|
||||||
|
@param packet: If packet is a list, it is copied into the C{TransferBuffer} and then normalized (see L{TransferBuffer._normalize}).
|
||||||
|
If it is an integer, a zero buffer of that length is created.
|
||||||
|
"""
|
||||||
|
if "__len__" in dir(packet):
|
||||||
|
list.__init__(self, list(packet))
|
||||||
|
self._normalize()
|
||||||
|
else: list.__init__(self, [0 for i in range(packet)])
|
||||||
|
|
||||||
|
def __add__(self, tb):
|
||||||
|
""" Return a TransferBuffer rather than a list as the sum """
|
||||||
|
return TransferBuffer(list.__add__(self, tb))
|
||||||
|
|
||||||
|
def __getslice__(self, start, end):
|
||||||
|
""" Return a TransferBuffer rather than a list as the slice """
|
||||||
|
return TransferBuffer(list.__getslice__(self, start, end))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Return a string representation of this buffer.
|
||||||
|
|
||||||
|
Packets are represented as hex strings, in 2-byte pairs, S{<=} 16 bytes to a line. An ASCII representation is included. For example::
|
||||||
|
0700 0100 0000 0000 0000 0000 0c00 0000 ................
|
||||||
|
0200 0000 0400 0000 4461 7461 ........Data
|
||||||
|
"""
|
||||||
|
ans, ascii = "", ""
|
||||||
|
for i in range(0, len(self), 2):
|
||||||
|
for b in range(2):
|
||||||
|
try:
|
||||||
|
ans += TransferBuffer.phex(self[i+b])
|
||||||
|
ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "."
|
||||||
|
except IndexError: break
|
||||||
|
ans = ans + " "
|
||||||
|
if (i+2)%16 == 0:
|
||||||
|
if i+2 < len(self):
|
||||||
|
ans += " " + ascii + "\n"
|
||||||
|
ascii = ""
|
||||||
|
last_line = ans[ans.rfind("\n")+1:]
|
||||||
|
padding = 40 - len(last_line)
|
||||||
|
ans += "".ljust(padding) + " " + ascii
|
||||||
|
return ans.strip()
|
||||||
|
|
||||||
|
def unpack(self, fmt=DWORD, start=0):
|
||||||
|
"""
|
||||||
|
Return decoded data from buffer.
|
||||||
|
|
||||||
|
@param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
|
||||||
|
@param start: Position in buffer from which to decode
|
||||||
|
"""
|
||||||
|
end = start + struct.calcsize(fmt)
|
||||||
|
return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ]))
|
||||||
|
|
||||||
|
def pack(self, val, fmt=DWORD, start=0):
|
||||||
|
"""
|
||||||
|
Encode C{val} and write it to buffer.
|
||||||
|
|
||||||
|
@param fmt: See U{struct<http://docs.python.org/lib/module-struct.html>}
|
||||||
|
@param start: Position in buffer at which to write encoded data
|
||||||
|
"""
|
||||||
|
self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ]
|
||||||
|
|
||||||
|
def _normalize(self):
|
||||||
|
""" Replace negative bytes in C{self} by 256 + byte """
|
||||||
|
for i in range(len(self)):
|
||||||
|
if self[i] < 0:
|
||||||
|
self[i] = 256 + self[i]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def phex(cls, num):
|
||||||
|
"""
|
||||||
|
Return the hex representation of num without the 0x prefix.
|
||||||
|
|
||||||
|
If the hex representation is only 1 digit it is padded to the left with a zero. Used in L{TransferBuffer.__str__}.
|
||||||
|
"""
|
||||||
|
index, sign = 2, ""
|
||||||
|
if num < 0:
|
||||||
|
index, sign = 3, "-"
|
||||||
|
h=hex(num)[index:]
|
||||||
|
if len(h) < 2:
|
||||||
|
h = "0"+h
|
||||||
|
return sign + h
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Command(TransferBuffer):
|
||||||
|
|
||||||
|
""" Defines the structure of command packets sent to the device. """
|
||||||
|
|
||||||
|
def __init__(self, packet):
|
||||||
|
"""
|
||||||
|
@param packet: len(packet) > 15 or packet > 15
|
||||||
|
"""
|
||||||
|
if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
|
||||||
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
|
||||||
|
TransferBuffer.__init__(self, packet)
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def number():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
Command number. C{unsigned int} stored in 4 bytes at byte 0.
|
||||||
|
|
||||||
|
Observed command numbers are:
|
||||||
|
1. 0x00
|
||||||
|
Test bulk read
|
||||||
|
2. 0x01
|
||||||
|
End session
|
||||||
|
3. 0x0101
|
||||||
|
Ask for device information
|
||||||
|
4. 0x1000
|
||||||
|
Acknowledge
|
||||||
|
5. 0x107
|
||||||
|
Purpose unknown, occurs in the beginning of sessions duing command testing. Best guess is some sort of OK packet
|
||||||
|
6. 0x106
|
||||||
|
Purpose unknown, occurs in the beginning of sessions duing command testing. Best guess is some sort of OK packet
|
||||||
|
7. 0x18
|
||||||
|
Ask for information about a file
|
||||||
|
8. 0x33
|
||||||
|
Open directory for reading
|
||||||
|
9. 0x34
|
||||||
|
Close directory
|
||||||
|
10. 0x35
|
||||||
|
Ask for next item in the directory
|
||||||
|
11. 0x10
|
||||||
|
File open command
|
||||||
|
12. 0x11
|
||||||
|
File close command
|
||||||
|
13. 0x16
|
||||||
|
File read command
|
||||||
|
"""
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=0, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=0, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def type():
|
||||||
|
doc =\
|
||||||
|
""" Command type. C{unsigned long long} stored in 8 bytes at byte 4. Known types 0x00, 0x01. Not sure what the type means. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=4, fmt=DDWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=4, fmt=DDWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def length():
|
||||||
|
doc =\
|
||||||
|
""" Length in bytes of the data part of the query. C{unsigned int} stored in 4 bytes at byte 12. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=12, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=12, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def data():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
The data part of this command. Returned/set as/by a TransferBuffer. Stored at byte 16.
|
||||||
|
|
||||||
|
Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
|
||||||
|
the significant part of the buffer. You would normally use the C{command} property of L{ShortCommand} or L{LongCommand} instead.
|
||||||
|
"""
|
||||||
|
def fget(self):
|
||||||
|
return self[16:]
|
||||||
|
|
||||||
|
def fset(self, buffer):
|
||||||
|
self[16:] = buffer
|
||||||
|
self.length = len(buffer)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
|
class ShortCommand(Command):
|
||||||
|
|
||||||
|
""" A L{Command} whoose data section is 4 bytes long """
|
||||||
|
|
||||||
|
SIZE = 20 #: Packet size in bytes
|
||||||
|
|
||||||
|
def __init__(self, number=0x00, type=0x00, command=0x00):
|
||||||
|
"""
|
||||||
|
@param number: L{Command.number}
|
||||||
|
@param type: L{Command.type}
|
||||||
|
@param command: L{ShortCommand.command}
|
||||||
|
"""
|
||||||
|
Command.__init__(self, ShortCommand.SIZE)
|
||||||
|
self.number = number
|
||||||
|
self.type = type
|
||||||
|
self.length = 4
|
||||||
|
self.command = command
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def command():
|
||||||
|
doc =\
|
||||||
|
""" The command. Not sure why this is needed in addition to Command.number. C{unsigned int} 4 bytes long at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class DirOpen(Command):
|
||||||
|
|
||||||
|
""" Open a directory for reading its contents """
|
||||||
|
NUMBER = 0x33 #: Command number
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
Command.__init__(self, 20 + len(path))
|
||||||
|
self.number=DirOpen.NUMBER
|
||||||
|
self.type = 0x01
|
||||||
|
self.length = 4 + len(path)
|
||||||
|
self.path_length = len(path)
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path_length():
|
||||||
|
doc =\
|
||||||
|
""" The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path():
|
||||||
|
doc =\
|
||||||
|
""" The path. Stored as a string at byte 20. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class DirRead(ShortCommand):
|
||||||
|
""" The command that asks the device to send the next item in the list """
|
||||||
|
NUMBER = 0x35 #: Command number
|
||||||
|
def __init__(self, id):
|
||||||
|
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
||||||
|
ShortCommand.__init__(self, number=DirRead.NUMBER, type=0x01, command=id)
|
||||||
|
|
||||||
|
class DirClose(ShortCommand):
|
||||||
|
""" Close a previously opened directory """
|
||||||
|
NUMBER = 0x34 #: Command number
|
||||||
|
def __init__(self, id):
|
||||||
|
""" @param id: The identifier returned as a result of a L{DirOpen} command """
|
||||||
|
ShortCommand.__init__(self, number=DirClose.NUMBER, type=0x01, command=id)
|
||||||
|
|
||||||
|
|
||||||
|
class LongCommand(Command):
|
||||||
|
|
||||||
|
""" A L{Command} whoose data section is 16 bytes long """
|
||||||
|
|
||||||
|
SIZE = 32 #: Size in bytes of C{LongCommand} packets
|
||||||
|
|
||||||
|
def __init__(self, number=0x00, type=0x00, command=0x00):
|
||||||
|
"""
|
||||||
|
@param number: L{Command.number}
|
||||||
|
@param type: L{Command.type}
|
||||||
|
@param command: L{LongCommand.command}
|
||||||
|
"""
|
||||||
|
Command.__init__(self, LongCommand.SIZE)
|
||||||
|
self.number = number
|
||||||
|
self.type = type
|
||||||
|
self.length = 16
|
||||||
|
self.command = command
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def command():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
The command. Not sure why it is needed in addition to L{Command.number}.
|
||||||
|
It is a list of C{unsigned integers} of length between 1 and 4. 4 C{unsigned int} stored in 16 bytes at byte 16.
|
||||||
|
"""
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if "__len__" not in dir(val): val = (val,)
|
||||||
|
start = 16
|
||||||
|
for command in val:
|
||||||
|
self.pack(command, start=start, fmt=DWORD)
|
||||||
|
start += struct.calcsize(DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
|
class AcknowledgeBulkRead(LongCommand):
|
||||||
|
|
||||||
|
""" Must be sent to device after a bulk read """
|
||||||
|
|
||||||
|
def __init__(self, bulk_read_id):
|
||||||
|
""" bulk_read_id is an integer, the id of the bulk read we are acknowledging. See L{Answer.id} """
|
||||||
|
LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
|
||||||
|
|
||||||
|
class DeviceInfoQuery(Command):
|
||||||
|
""" The command used to ask for device information """
|
||||||
|
NUMBER=0x0101 #: Command number
|
||||||
|
def __init__(self):
|
||||||
|
Command.__init__(self, 16)
|
||||||
|
self.number=DeviceInfoQuery.NUMBER
|
||||||
|
self.type=0x01
|
||||||
|
|
||||||
|
class FileClose(ShortCommand):
|
||||||
|
""" File close command """
|
||||||
|
NUMBER = 0x11 #: Command number
|
||||||
|
def __init__(self, id):
|
||||||
|
ShortCommand.__init__(self, number=FileClose.NUMBER, type=0x01, command=id)
|
||||||
|
|
||||||
|
class FileOpen(Command):
|
||||||
|
""" File open command """
|
||||||
|
NUMBER = 0x10
|
||||||
|
READ = 0x00
|
||||||
|
WRITE = 0x01
|
||||||
|
def __init__(self, path, mode=0x00):
|
||||||
|
Command.__init__(self, 24 + len(path))
|
||||||
|
self.number=FileOpen.NUMBER
|
||||||
|
self.type = 0x01
|
||||||
|
self.length = 8 + len(path)
|
||||||
|
self.mode = mode
|
||||||
|
self.path_length = len(path)
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def mode():
|
||||||
|
doc =\
|
||||||
|
""" The file open mode. Is either L{FileOpen.READ} or L{FileOpen.WRITE}. C{unsigned int} stored at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path_length():
|
||||||
|
doc =\
|
||||||
|
""" The length in bytes of the path to follow. C{unsigned int} stored at byte 20. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path():
|
||||||
|
doc =\
|
||||||
|
""" The path. Stored as a string at byte 24. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=24, fmt="<"+str(self.path_length)+"s")[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=24, fmt="<"+str(self.path_length)+"s")
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class FileRead(Command):
|
||||||
|
""" Command to read from an open file """
|
||||||
|
NUMBER = 0x16 #: Command number to read from a file
|
||||||
|
def __init__(self, id, offset, size):
|
||||||
|
"""
|
||||||
|
@param id: File identifier returned by a L{FileOpen} command
|
||||||
|
@type id: C{unsigned int}
|
||||||
|
@param offset: Position in file at which to read
|
||||||
|
@type offset: C{unsigned long long}
|
||||||
|
@param size: number of bytes to read
|
||||||
|
@type size: C{unsigned int}
|
||||||
|
"""
|
||||||
|
Command.__init__(self, 32)
|
||||||
|
self.number=FileRead.NUMBER
|
||||||
|
self.type = 0x01
|
||||||
|
self.length = 32
|
||||||
|
self.id = id
|
||||||
|
self.offset = offset
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def id():
|
||||||
|
doc =\
|
||||||
|
""" The file ID returned by a FileOpen command. C{unsigned int} stored in 4 bytes at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def offset():
|
||||||
|
doc =\
|
||||||
|
""" offset in the file at which to read. C{unsigned long long} stored in 8 bytes at byte 20. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt=DDWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt=DDWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def size():
|
||||||
|
doc =\
|
||||||
|
""" The number of bytes to read. C{unsigned int} stored in 4 bytes at byte 28. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=28, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=28, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PathQuery(Command):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Defines structure of command that requests information about a path
|
||||||
|
|
||||||
|
>>> print prstypes.PathQuery("/test/path/", number=prstypes.PathQuery.PROPERTIES)
|
||||||
|
1800 0000 0100 0000 0000 0000 0f00 0000 ................
|
||||||
|
0b00 0000 2f74 6573 742f 7061 7468 2f ..../test/path/
|
||||||
|
"""
|
||||||
|
NUMBER = 0x18 #: Command number
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
Command.__init__(self, 20 + len(path))
|
||||||
|
self.number=PathQuery.NUMBER
|
||||||
|
self.type = 0x01
|
||||||
|
self.length = 4 + len(path)
|
||||||
|
self.path_length = len(path)
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path_length():
|
||||||
|
doc =\
|
||||||
|
""" The length in bytes of the path to follow. C{unsigned int} stored at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path():
|
||||||
|
doc =\
|
||||||
|
""" The path. Stored as a string at byte 20. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
|
||||||
|
class Response(Command):
|
||||||
|
"""
|
||||||
|
Defines the structure of response packets received from the device.
|
||||||
|
|
||||||
|
C{Response} inherits from C{Command} as the first 16 bytes have the same structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SIZE = 32 #: Size of response packets in the SONY protocol
|
||||||
|
|
||||||
|
def __init__(self, packet):
|
||||||
|
""" C{len(packet) == Response.SIZE} """
|
||||||
|
if len(packet) != Response.SIZE:
|
||||||
|
raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
|
||||||
|
Command.__init__(self, packet)
|
||||||
|
if self.number != 0x00001000:
|
||||||
|
raise PacketError("Response packets must have their number set to " + hex(0x00001000))
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def rnumber():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
The response number. C{unsigned int} stored in 4 bytes at byte 16.
|
||||||
|
|
||||||
|
It will be the command number from a command that was sent to the device sometime before this response.
|
||||||
|
"""
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def data():
|
||||||
|
doc =\
|
||||||
|
""" The last 3 DWORDs (12 bytes) of data in this response packet. Returned as a list of unsigned integers. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt="<III")
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt="<III")
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class ListResponse(Response):
|
||||||
|
|
||||||
|
""" Defines the structure of response packets received during list (ll) queries. See L{PathQuery}. """
|
||||||
|
|
||||||
|
IS_FILE = 0xffffffd2 #: Queried path is a file
|
||||||
|
IS_INVALID = 0xfffffff9 #: Queried path is malformed/invalid
|
||||||
|
IS_UNMOUNTED = 0xffffffc8 #: Queried path is not mounted (i.e. a removed storage card/stick)
|
||||||
|
IS_EOL = 0xfffffffa #: There are no more entries in the list
|
||||||
|
PATH_NOT_FOUND = 0xffffffd7 #: Queried path is not found
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def code():
|
||||||
|
doc =\
|
||||||
|
""" The response code. Used to indicate conditions like EOL/Error/IsFile etc. C{unsigned int} stored in 4 bytes at byte 20. """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt=DDWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt=DDWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_file():
|
||||||
|
""" True iff queried path is a file """
|
||||||
|
def fget(self):
|
||||||
|
return self.code == ListResponse.IS_FILE
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_invalid():
|
||||||
|
""" True iff queried path is invalid """
|
||||||
|
def fget(self):
|
||||||
|
return self.code == ListResponse.IS_INVALID
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def path_not_found():
|
||||||
|
""" True iff queried path is not found """
|
||||||
|
def fget(self):
|
||||||
|
return self.code == ListResponse.PATH_NOT_FOUND
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_unmounted():
|
||||||
|
""" True iff queried path is unmounted (i.e. removed storage card) """
|
||||||
|
def fget(self):
|
||||||
|
return self.code == ListResponse.IS_UNMOUNTED
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_eol():
|
||||||
|
""" True iff there are no more items in the list """
|
||||||
|
def fget(self):
|
||||||
|
return self.code == ListResponse.IS_EOL
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class Answer(TransferBuffer):
|
||||||
|
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
||||||
|
|
||||||
|
def __init__(self, packet):
|
||||||
|
""" @param packet: C{len(packet)} S{>=} C{16} """
|
||||||
|
if len(packet) < 16 : raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
||||||
|
TransferBuffer.__init__(self, packet)
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def id():
|
||||||
|
doc =\
|
||||||
|
""" The id of this bulk transfer packet. C{unsigned int} stored in 4 bytes at byte 0. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=0, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=0, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class FileProperties(Answer):
|
||||||
|
|
||||||
|
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def file_size():
|
||||||
|
doc =\
|
||||||
|
""" The file size. C{unsigned long long} stored in 8 bytes at byte 16. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DDWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DDWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_dir():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
True if path points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 24.
|
||||||
|
|
||||||
|
Value of 1 == file and 2 == dir
|
||||||
|
"""
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return (self.unpack(start=24, fmt=DWORD)[0] == 2)
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if val: val = 2
|
||||||
|
else: val = 1
|
||||||
|
self.pack(val, start=24, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def ctime():
|
||||||
|
doc =\
|
||||||
|
""" The creation time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 28. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=28, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=28, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def wtime():
|
||||||
|
doc =\
|
||||||
|
""" The modification time of this file/dir as an epoch (seconds since Jan 1970). C{unsigned int} stored in 4 bytes at byte 32"""
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=32, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=32, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_readonly():
|
||||||
|
doc =\
|
||||||
|
"""
|
||||||
|
Whether this file is readonly. C{unsigned int} stored in 4 bytes at byte 36.
|
||||||
|
|
||||||
|
A value of 0 corresponds to read/write and 4 corresponds to read-only. The device doesn't send full permissions information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if val: val = 4
|
||||||
|
else: val = 0
|
||||||
|
self.pack(val, start=36, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class IdAnswer(Answer):
|
||||||
|
|
||||||
|
""" Defines the structure of packets that contain identifiers for queries. """
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def id():
|
||||||
|
doc =\
|
||||||
|
""" The identifier. C{unsigned int} stored in 4 bytes at byte 16. Should be sent in commands asking for the next item in the list. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=16, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class DeviceInfo(Answer):
|
||||||
|
""" Defines the structure of the packet containing information about the device """
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def device_name():
|
||||||
|
""" The name of the device. Stored as a string in 32 bytes starting at byte 16. """
|
||||||
|
def fget(self):
|
||||||
|
src = self.unpack(start=16, fmt="<32s")[0]
|
||||||
|
return src[0:src.find('\x00')]
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def device_version():
|
||||||
|
""" The device version. Stored as a string in 32 bytes starting at byte 48. """
|
||||||
|
def fget(self):
|
||||||
|
src = self.unpack(start=48, fmt="<32s")[0]
|
||||||
|
return src[0:src.find('\x00')]
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def software_version():
|
||||||
|
""" Version of the software on the device. Stored as a string in 26 bytes starting at byte 80. """
|
||||||
|
def fget(self):
|
||||||
|
src = self.unpack(start=80, fmt="<26s")[0]
|
||||||
|
return src[0:src.find('\x00')]
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def mime_type():
|
||||||
|
""" Mime type served by tinyhttp?. Stored as a string in 32 bytes starting at byte 104. """
|
||||||
|
def fget(self):
|
||||||
|
src = self.unpack(start=104, fmt="<32s")[0]
|
||||||
|
return src[0:src.find('\x00')]
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
class ListAnswer(Answer):
|
||||||
|
|
||||||
|
""" Defines the structure of packets that contain items in a list. """
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def is_dir():
|
||||||
|
doc =\
|
||||||
|
""" True if list item points to a directory, False if it points to a file. C{unsigned int} stored in 4 bytes at byte 16. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return (self.unpack(start=16, fmt=DWORD)[0] == 2)
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
if val: val = 2
|
||||||
|
else: val = 1
|
||||||
|
self.pack(val, start=16, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def name_length():
|
||||||
|
doc =\
|
||||||
|
""" The length in bytes of the list item to follow. C{unsigned int} stored in 4 bytes at byte 20 """
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=20, fmt=DWORD)[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=20, fmt=DWORD)
|
||||||
|
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def name():
|
||||||
|
doc =\
|
||||||
|
""" The name of the list item. Stored as an (ascii?) string at byte 24. """
|
||||||
|
|
||||||
|
def fget(self):
|
||||||
|
return self.unpack(start=24, fmt="<"+str(self.name_length)+"s")[0]
|
||||||
|
|
||||||
|
def fset(self, val):
|
||||||
|
self.pack(val, start=24, fmt="<"+str(self.name_length)+"s")
|
||||||
|
|
||||||
|
return property(**locals())
|
101
pr5-500.e3p
101
pr5-500.e3p
@ -1,101 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE Project SYSTEM "Project-3.9.dtd">
|
|
||||||
<!-- Project file for project pr5-500 -->
|
|
||||||
<!-- Saved: 2006-10-31, 09:35:02 -->
|
|
||||||
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
|
|
||||||
<Project version="3.9">
|
|
||||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
|
||||||
<UIType>Console</UIType>
|
|
||||||
<Description>Library to communicate with the Sony Reader prs-500 via USB</Description>
|
|
||||||
<Version></Version>
|
|
||||||
<Author>Kovid Goyal</Author>
|
|
||||||
<Email>kovid@kovidgoyal.net</Email>
|
|
||||||
<Sources>
|
|
||||||
<Source>
|
|
||||||
<Name>communicate.py</Name>
|
|
||||||
</Source>
|
|
||||||
<Source>
|
|
||||||
<Name>data.py</Name>
|
|
||||||
</Source>
|
|
||||||
<Source>
|
|
||||||
<Name>prstypes.py</Name>
|
|
||||||
</Source>
|
|
||||||
</Sources>
|
|
||||||
<Forms>
|
|
||||||
</Forms>
|
|
||||||
<Translations>
|
|
||||||
</Translations>
|
|
||||||
<Interfaces>
|
|
||||||
</Interfaces>
|
|
||||||
<Others>
|
|
||||||
</Others>
|
|
||||||
<MainScript>
|
|
||||||
<Name>communicate.py</Name>
|
|
||||||
</MainScript>
|
|
||||||
<Vcs>
|
|
||||||
<VcsType>Subversion</VcsType>
|
|
||||||
<VcsOptions>(dp0
|
|
||||||
S'status'
|
|
||||||
p1
|
|
||||||
(lp2
|
|
||||||
S''
|
|
||||||
p3
|
|
||||||
asS'log'
|
|
||||||
p4
|
|
||||||
(lp5
|
|
||||||
g3
|
|
||||||
asS'global'
|
|
||||||
p6
|
|
||||||
(lp7
|
|
||||||
g3
|
|
||||||
asS'update'
|
|
||||||
p8
|
|
||||||
(lp9
|
|
||||||
g3
|
|
||||||
asS'remove'
|
|
||||||
p10
|
|
||||||
(lp11
|
|
||||||
g3
|
|
||||||
asS'add'
|
|
||||||
p12
|
|
||||||
(lp13
|
|
||||||
g3
|
|
||||||
asS'tag'
|
|
||||||
p14
|
|
||||||
(lp15
|
|
||||||
g3
|
|
||||||
asS'export'
|
|
||||||
p16
|
|
||||||
(lp17
|
|
||||||
g3
|
|
||||||
asS'commit'
|
|
||||||
p18
|
|
||||||
(lp19
|
|
||||||
g3
|
|
||||||
asS'diff'
|
|
||||||
p20
|
|
||||||
(lp21
|
|
||||||
g3
|
|
||||||
asS'checkout'
|
|
||||||
p22
|
|
||||||
(lp23
|
|
||||||
g3
|
|
||||||
asS'history'
|
|
||||||
p24
|
|
||||||
(lp25
|
|
||||||
g3
|
|
||||||
as.</VcsOptions>
|
|
||||||
<VcsOtherData>(dp0
|
|
||||||
S'standardLayout'
|
|
||||||
p1
|
|
||||||
I01
|
|
||||||
s.</VcsOtherData>
|
|
||||||
</Vcs>
|
|
||||||
<FiletypeAssociations>
|
|
||||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
|
||||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
|
||||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
|
||||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
|
||||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
|
||||||
</FiletypeAssociations>
|
|
||||||
</Project>
|
|
12
prs-500.e3p
12
prs-500.e3p
@ -6,22 +6,22 @@
|
|||||||
<Project version="3.9">
|
<Project version="3.9">
|
||||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||||
<UIType>Console</UIType>
|
<UIType>Console</UIType>
|
||||||
<Description>Library to communicate with the Sony Reader prs-500 via USB</Description>
|
<Description>Library to communicate with the Sony Reader PRS-500 via USB</Description>
|
||||||
<Version></Version>
|
<Version></Version>
|
||||||
<Author>Kovid Goyal</Author>
|
<Author>Kovid Goyal</Author>
|
||||||
<Email>kovid@kovidgoyal.net</Email>
|
<Email>kovid@kovidgoyal.net</Email>
|
||||||
<Sources>
|
<Sources>
|
||||||
<Source>
|
<Source>
|
||||||
<Name>communicate.py</Name>
|
<Name>libprs500/communicate.py</Name>
|
||||||
</Source>
|
</Source>
|
||||||
<Source>
|
<Source>
|
||||||
<Name>data.py</Name>
|
<Name>libprs500/terminfo.py</Name>
|
||||||
</Source>
|
</Source>
|
||||||
<Source>
|
<Source>
|
||||||
<Name>prstypes.py</Name>
|
<Name>libprs500/prstypes.py</Name>
|
||||||
</Source>
|
</Source>
|
||||||
<Source>
|
<Source>
|
||||||
<Name>errors.py</Name>
|
<Name>libprs500/errors.py</Name>
|
||||||
</Source>
|
</Source>
|
||||||
</Sources>
|
</Sources>
|
||||||
<Forms>
|
<Forms>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<Others>
|
<Others>
|
||||||
</Others>
|
</Others>
|
||||||
<MainScript>
|
<MainScript>
|
||||||
<Name>communicate.py</Name>
|
<Name>libprs500/communicate.py</Name>
|
||||||
</MainScript>
|
</MainScript>
|
||||||
<Vcs>
|
<Vcs>
|
||||||
<VcsType>Subversion</VcsType>
|
<VcsType>Subversion</VcsType>
|
||||||
|
533
prstypes.py
533
prstypes.py
@ -1,533 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
|
||||||
## 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.,
|
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
|
|
||||||
import struct
|
|
||||||
from errors import PacketError
|
|
||||||
|
|
||||||
BYTE = "<B" # Unsigned char little endian encoded in 1 byte
|
|
||||||
WORD = "<H" # Unsigned short little endian encoded in 2 bytes
|
|
||||||
DWORD = "<I" # Unsigned integer little endian encoded in 4 bytes
|
|
||||||
DDWORD = "<Q" # Unsigned long long little endian encoded in 8 bytes
|
|
||||||
|
|
||||||
|
|
||||||
class TransferBuffer(list):
|
|
||||||
def __init__(self, packet):
|
|
||||||
"""
|
|
||||||
packet should be any listable object, or an integer. If it is an integer, a zero buffer of that length is created.
|
|
||||||
packet is normalized (see TransferBuffer.normalize)
|
|
||||||
"""
|
|
||||||
if "__len__" in dir(packet):
|
|
||||||
list.__init__(self, list(packet))
|
|
||||||
self.normalize()
|
|
||||||
else: list.__init__(self, [0 for i in range(packet)])
|
|
||||||
|
|
||||||
def __add__(self, tb):
|
|
||||||
""" Return a TransferBuffer rather thana list as the sum """
|
|
||||||
return TransferBuffer(list.__add__(self, tb))
|
|
||||||
|
|
||||||
def __getslice__(self, start, end):
|
|
||||||
""" Return a TransferBuffer rather than a list as the slice """
|
|
||||||
return TransferBuffer(list.__getslice__(self, start, end))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
Return a string representation of this buffer in the same format as that produced by spike.pl
|
|
||||||
"""
|
|
||||||
ans, ascii = "", ""
|
|
||||||
for i in range(0, len(self), 2):
|
|
||||||
for b in range(2):
|
|
||||||
try:
|
|
||||||
ans += TransferBuffer.phex(self[i+b])
|
|
||||||
ascii += chr(self[i+b]) if self[i+b] > 31 and self[i+b] < 127 else "."
|
|
||||||
except IndexError: break
|
|
||||||
ans = ans + " "
|
|
||||||
if (i+2)%16 == 0:
|
|
||||||
ans += "\t" + ascii + "\n"
|
|
||||||
ascii = ""
|
|
||||||
if len(ascii) > 0:
|
|
||||||
last_line = ans[ans.rfind("\n")+1:]
|
|
||||||
padding = 32 - len(last_line)
|
|
||||||
ans += "".ljust(padding) + "\t\t" + ascii
|
|
||||||
return ans.strip()
|
|
||||||
|
|
||||||
def unpack(self, fmt=DWORD, start=0):
|
|
||||||
""" Return decoded data from buffer. See pydoc struct for fmt. start is position in buffer from which to decode. """
|
|
||||||
end = start + struct.calcsize(fmt)
|
|
||||||
return struct.unpack(fmt, "".join([ chr(i) for i in list.__getslice__(self, start, end) ]))
|
|
||||||
|
|
||||||
def pack(self, val, fmt=DWORD, start=0):
|
|
||||||
""" Encode data and write it to buffer. See pydoc struct fmt. start is position in buffer at which to write encoded data. """
|
|
||||||
self[start:start+struct.calcsize(fmt)] = [ ord(i) for i in struct.pack(fmt, val) ]
|
|
||||||
|
|
||||||
def normalize(self):
|
|
||||||
""" Replace negative bytes by 256 + byte """
|
|
||||||
for i in range(len(self)):
|
|
||||||
if self[i] < 0:
|
|
||||||
self[i] = 256 + self[i]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def phex(cls, num):
|
|
||||||
"""
|
|
||||||
Return the hex representation of num without the 0x prefix.
|
|
||||||
|
|
||||||
If the hex representation is only 1 digit it is padded to the left with a zero.
|
|
||||||
"""
|
|
||||||
index, sign = 2, ""
|
|
||||||
if num < 0:
|
|
||||||
index, sign = 3, "-"
|
|
||||||
h=hex(num)[index:]
|
|
||||||
if len(h) < 2:
|
|
||||||
h = "0"+h
|
|
||||||
return sign + h
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Command(TransferBuffer):
|
|
||||||
""" Defines the structure of command packets sent to the device. """
|
|
||||||
|
|
||||||
def __init__(self, packet):
|
|
||||||
if ("__len__" in dir(packet) and len(packet) < 16) or ("__len__" not in dir(packet) and packet < 16):
|
|
||||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have length atleast 16")
|
|
||||||
TransferBuffer.__init__(self, packet)
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def number():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
Command number
|
|
||||||
|
|
||||||
Observed command numbers are:
|
|
||||||
0x00001000 -- Acknowledge
|
|
||||||
0x00000107 -- Purpose unknown, occurs in start_session
|
|
||||||
0x00000106 -- Purpose unknown, occurs in start_session
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=0, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=0, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def type():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
Command type. Known types 0x00, 0x01. Not sure what the type means.
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=4, fmt=DDWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=4, fmt=DDWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def length():
|
|
||||||
doc =\
|
|
||||||
""" Length in bytes of the data part of the query """
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=12, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=12, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def data():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
The data part of this command. Returned/set as/by a TransferBuffer.
|
|
||||||
|
|
||||||
Setting it by default changes self.length to the length of the new buffer. You may have to reset it to
|
|
||||||
the significant part of the buffer.
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self[16:]
|
|
||||||
|
|
||||||
def fset(self, buffer):
|
|
||||||
self[16:] = buffer
|
|
||||||
self.length = len(buffer)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
|
|
||||||
class ShortCommand(Command):
|
|
||||||
|
|
||||||
SIZE = 20 #Packet size in bytes
|
|
||||||
|
|
||||||
def __init__(self, number=0x00, type=0x00, command=0x00):
|
|
||||||
""" command must be an integer """
|
|
||||||
Command.__init__(self, ShortCommand.SIZE)
|
|
||||||
self.number = number
|
|
||||||
self.type = type
|
|
||||||
self.length = 4
|
|
||||||
self.command = command
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def list_command(cls, id):
|
|
||||||
""" Return the command packet used to ask for the next item in the list """
|
|
||||||
return ShortCommand(number=0x35, type=0x01, command=id)
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def command():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
The command. Not sure why this is needed in addition to Command.number
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=16, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class LongCommand(Command):
|
|
||||||
|
|
||||||
SIZE = 32 # Size in bytes of long command packets
|
|
||||||
|
|
||||||
def __init__(self, number=0x00, type=0x00, command=0x00):
|
|
||||||
""" command must be either an integer or a list of not more than 4 integers """
|
|
||||||
Command.__init__(self, LongCommand.SIZE)
|
|
||||||
self.number = number
|
|
||||||
self.type = type
|
|
||||||
self.length = 16
|
|
||||||
self.command = command
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def command():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
The command.
|
|
||||||
|
|
||||||
It should be set to a x-integer list, where 0<x<5.
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt="<"+str(self.length/4)+"I")
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if "__len__" not in dir(val): val = (val,)
|
|
||||||
start = 16
|
|
||||||
for command in val:
|
|
||||||
self.pack(command, start=start, fmt=DWORD)
|
|
||||||
start += struct.calcsize(DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
|
|
||||||
class AcknowledgeBulkRead(LongCommand):
|
|
||||||
""" Must be sent to device after a bulk read """
|
|
||||||
|
|
||||||
def __init__(self, bulk_read_id):
|
|
||||||
""" bulk_read_id is an integer, the id of the bulk read we are acknowledging """
|
|
||||||
LongCommand.__init__(self, number=0x1000, type=0x00, command=bulk_read_id)
|
|
||||||
|
|
||||||
class PathQuery(Command):
|
|
||||||
|
|
||||||
""" Defines structure of commands that request information about a path """
|
|
||||||
|
|
||||||
# Command.number values used in path queries
|
|
||||||
PROPERTIES = 0x18 # Ask for file properties
|
|
||||||
ID = 0x33 # Ask for query id for a directory listing
|
|
||||||
|
|
||||||
def __init__(self, path, number=0x18):
|
|
||||||
Command.__init__(self, 20 + len(path))
|
|
||||||
self.number=number
|
|
||||||
self.type = 0x01
|
|
||||||
self.length = 4 + len(path)
|
|
||||||
self.path_length = len(path)
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def path_length():
|
|
||||||
doc =\
|
|
||||||
""" The length in bytes of the path to follow """
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=16, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def path():
|
|
||||||
doc =\
|
|
||||||
""" The path """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=20, fmt="<"+str(self.path_length)+"s")[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=20, fmt="<"+str(self.path_length)+"s")
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
|
|
||||||
class Response(Command):
|
|
||||||
""" Defines the structure of response packets received from the device. """
|
|
||||||
|
|
||||||
SIZE = 32 # Size of response packets in the SONY protocol
|
|
||||||
|
|
||||||
def __init__(self, packet):
|
|
||||||
""" len(packet) == Response.SIZE """
|
|
||||||
if len(packet) != Response.SIZE:
|
|
||||||
raise PacketError(str(self.__class__)[7:-2] + " packets must have exactly " + str(Response.SIZE) + " bytes not " + str(len(packet)))
|
|
||||||
Command.__init__(self, packet)
|
|
||||||
if self.number != 0x00001000:
|
|
||||||
raise PacketError("Response packets must have their number set to " + hex(0x00001000))
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def rnumber():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
The response number.
|
|
||||||
|
|
||||||
It will be the command number from a command that was sent to the device sometime before this response.
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=16, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def data():
|
|
||||||
doc =\
|
|
||||||
""" The last 3 DWORDs of data in this response packet. Returned as a list. """
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=20, fmt="<III")
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=20, fmt="<III")
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class ListResponse(Response):
|
|
||||||
|
|
||||||
""" Defines the structure of response packets received during list (ll) queries """
|
|
||||||
|
|
||||||
IS_FILE = 0xffffffd2
|
|
||||||
IS_INVALID = 0xfffffff9
|
|
||||||
IS_UNMOUNTED = 0xffffffc8
|
|
||||||
IS_EOL = 0xfffffffa
|
|
||||||
PATH_NOT_FOUND = 0xffffffd7
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def code():
|
|
||||||
doc =\
|
|
||||||
"""
|
|
||||||
The response code. Used to indicate conditions like EOL/Error/IsFile
|
|
||||||
|
|
||||||
fmt=DWORD
|
|
||||||
"""
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=20, fmt=DDWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=20, fmt=DDWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_file():
|
|
||||||
def fget(self):
|
|
||||||
return self.code == ListResponse.IS_FILE
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_invalid():
|
|
||||||
def fget(self):
|
|
||||||
return self.code == ListResponse.IS_INVALID
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def path_not_found():
|
|
||||||
def fget(self):
|
|
||||||
return self.code == ListResponse.PATH_NOT_FOUND
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_unmounted():
|
|
||||||
def fget(self):
|
|
||||||
return self.code == ListResponse.IS_UNMOUNTED
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_eol():
|
|
||||||
def fget(self):
|
|
||||||
return self.code == ListResponse.IS_EOL
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class Answer(TransferBuffer):
|
|
||||||
""" Defines the structure of packets sent to host via a bulk transfer (i.e., bulk reads) """
|
|
||||||
|
|
||||||
def __init__(self, packet):
|
|
||||||
""" packet must be a listable object of length >= 16 """
|
|
||||||
if len(packet) < 16 : raise PacketError(str(self.__class__)[7:-2] + " packets must have a length of atleast 16 bytes")
|
|
||||||
TransferBuffer.__init__(self, packet)
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def id():
|
|
||||||
doc =\
|
|
||||||
""" The id of this bulk transfer packet """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=0, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=0, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class PathAnswer(Answer):
|
|
||||||
|
|
||||||
""" Defines the structure of packets that contain size, date and permissions information about files/directories. """
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def file_size():
|
|
||||||
doc =\
|
|
||||||
""" The file size """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt=DDWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=16, fmt=DDWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_dir():
|
|
||||||
doc =\
|
|
||||||
""" True if path points to a directory, False if it points to a file """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return (self.unpack(start=24, fmt=DWORD)[0] == 2)
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if val: val = 2
|
|
||||||
else: val = 1
|
|
||||||
self.pack(val, start=24, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def ctime():
|
|
||||||
doc =\
|
|
||||||
""" The creation time of this file/dir as an epoch """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=28, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=28, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def wtime():
|
|
||||||
doc =\
|
|
||||||
""" The modification time of this file/dir as an epoch """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=32, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=32, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_readonly():
|
|
||||||
doc =\
|
|
||||||
""" Whether this file is readonly """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=36, fmt=DWORD)[0] != 0
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if val: val = 4
|
|
||||||
else: val = 0
|
|
||||||
self.pack(val, start=36, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class IdAnswer(Answer):
|
|
||||||
|
|
||||||
""" Defines the structure of packets that contain identifiers for directories. """
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def id():
|
|
||||||
doc =\
|
|
||||||
""" The identifier """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=16, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=16, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
class ListAnswer(Answer):
|
|
||||||
|
|
||||||
""" Defines the structure of packets that contain items in a list. """
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def is_dir():
|
|
||||||
doc =\
|
|
||||||
""" True if list item points to a directory, False if it points to a file """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return (self.unpack(start=16, fmt=DWORD)[0] == 2)
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
if val: val = 2
|
|
||||||
else: val = 1
|
|
||||||
self.pack(val, start=16, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def name_length():
|
|
||||||
doc =\
|
|
||||||
""" The length in bytes of the list item to follow """
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=20, fmt=DWORD)[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=20, fmt=DWORD)
|
|
||||||
|
|
||||||
return property(**locals())
|
|
||||||
|
|
||||||
@apply
|
|
||||||
def name():
|
|
||||||
doc =\
|
|
||||||
""" The name of the list item """
|
|
||||||
|
|
||||||
def fget(self):
|
|
||||||
return self.unpack(start=24, fmt="<"+str(self.name_length)+"s")[0]
|
|
||||||
|
|
||||||
def fset(self, val):
|
|
||||||
self.pack(val, start=24, fmt="<"+str(self.name_length)+"s")
|
|
||||||
|
|
||||||
return property(**locals())
|
|
244
scripts/prs500.py
Executable file
244
scripts/prs500.py
Executable file
@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Provides a command-line interface to the SONY Reader PRS-500.
|
||||||
|
|
||||||
|
For usage information run the script.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import StringIO, sys, time, os
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
from libprs500 import VERSION
|
||||||
|
from libprs500.communicate import PRS500Device
|
||||||
|
from libprs500.terminfo import TerminalController
|
||||||
|
from libprs500.errors import ArgumentError
|
||||||
|
|
||||||
|
|
||||||
|
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||||
|
|
||||||
|
class FileFormatter(object):
|
||||||
|
def __init__(self, file, term):
|
||||||
|
self.term = term
|
||||||
|
self.is_dir = file.is_dir
|
||||||
|
self.is_readonly = file.is_readonly
|
||||||
|
self.size = file.size
|
||||||
|
self.ctime = file.ctime
|
||||||
|
self.wtime = file.wtime
|
||||||
|
self.name = file.name
|
||||||
|
self.path = file.path
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def mode_string():
|
||||||
|
doc=""" The mode string for this file. There are only two modes read-only and read-write """
|
||||||
|
def fget(self):
|
||||||
|
mode, x = "-", "-"
|
||||||
|
if self.is_dir: mode, x = "d", "x"
|
||||||
|
if self.is_readonly: mode += "r-"+x+"r-"+x+"r-"+x
|
||||||
|
else: mode += "rw"+x+"rw"+x+"rw"+x
|
||||||
|
return mode
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def name_in_color():
|
||||||
|
doc=""" The name in ANSI text. Directories are blue, ebooks are green """
|
||||||
|
def fget(self):
|
||||||
|
cname = self.name
|
||||||
|
blue, green, normal = "", "", ""
|
||||||
|
if self.term: blue, green, normal = self.term.BLUE, self.term.GREEN, self.term.NORMAL
|
||||||
|
if self.is_dir: cname = blue + self.name + normal
|
||||||
|
else:
|
||||||
|
ext = self.name[self.name.rfind("."):]
|
||||||
|
if ext in (".pdf", ".rtf", ".lrf", ".lrx", ".txt"): cname = green + self.name + normal
|
||||||
|
return cname
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def human_readable_size():
|
||||||
|
doc=""" File size in human readable form """
|
||||||
|
def fget(self):
|
||||||
|
if self.size < 1024: divisor, suffix = 1, ""
|
||||||
|
elif self.size < 1024*1024: divisor, suffix = 1024., "K"
|
||||||
|
elif self.size < 1024*1024*1024: divisor, suffix = 1024*1024, "M"
|
||||||
|
elif self.size < 1024*1024*1024*1024: divisor, suffix = 1024*1024, "G"
|
||||||
|
size = str(self.size/divisor)
|
||||||
|
if size.find(".") > -1: size = size[:size.find(".")+2]
|
||||||
|
return size + suffix
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def modification_time():
|
||||||
|
doc=""" Last modified time in the Linux ls -l format """
|
||||||
|
def fget(self):
|
||||||
|
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.wtime))
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
@apply
|
||||||
|
def creation_time():
|
||||||
|
doc=""" Last modified time in the Linux ls -l format """
|
||||||
|
def fget(self):
|
||||||
|
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.ctime))
|
||||||
|
return property(**locals())
|
||||||
|
|
||||||
|
def info(dev):
|
||||||
|
info = dev.get_device_information()
|
||||||
|
print "Device name: ", info[0]
|
||||||
|
print "Device version: ", info[1]
|
||||||
|
print "Software version:", info[2]
|
||||||
|
print "Mime type: ", info[3]
|
||||||
|
|
||||||
|
def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
|
||||||
|
def col_split(l, cols): # split list l into columns
|
||||||
|
rows = len(l) / cols
|
||||||
|
if len(l) % cols:
|
||||||
|
rows += 1
|
||||||
|
m = []
|
||||||
|
for i in range(rows):
|
||||||
|
m.append(l[i::rows])
|
||||||
|
return m
|
||||||
|
|
||||||
|
def row_widths(table): # Calculate widths for each column in the row-wise table
|
||||||
|
tcols = len(table[0])
|
||||||
|
rowwidths = [ 0 for i in range(tcols) ]
|
||||||
|
for row in table:
|
||||||
|
c = 0
|
||||||
|
for item in row:
|
||||||
|
rowwidths[c] = len(item) if len(item) > rowwidths[c] else rowwidths[c]
|
||||||
|
c += 1
|
||||||
|
return rowwidths
|
||||||
|
|
||||||
|
output = StringIO.StringIO()
|
||||||
|
if path.endswith("/"): path = path[:-1]
|
||||||
|
dirs = dev.list(path, recurse)
|
||||||
|
for dir in dirs:
|
||||||
|
if recurse: print >>output, dir[0] + ":"
|
||||||
|
lsoutput, lscoloutput = [], []
|
||||||
|
files = dir[1]
|
||||||
|
maxlen = 0
|
||||||
|
if ll: # Calculate column width for size column
|
||||||
|
for file in files:
|
||||||
|
size = len(str(file.size))
|
||||||
|
if human_readable_size:
|
||||||
|
file = FileFormatter(file, term)
|
||||||
|
size = len(file.human_readable_size)
|
||||||
|
if size > maxlen: maxlen = size
|
||||||
|
for file in files:
|
||||||
|
file = FileFormatter(file, term)
|
||||||
|
name = file.name
|
||||||
|
lsoutput.append(name)
|
||||||
|
if color: name = file.name_in_color
|
||||||
|
lscoloutput.append(name)
|
||||||
|
if ll:
|
||||||
|
size = str(file.size)
|
||||||
|
if human_readable_size: size = file.human_readable_size
|
||||||
|
print >>output, file.mode_string, ("%"+str(maxlen)+"s")%size, file.modification_time, name
|
||||||
|
if not ll and len(lsoutput) > 0:
|
||||||
|
trytable = []
|
||||||
|
for colwidth in range(MINIMUM_COL_WIDTH, cols):
|
||||||
|
trycols = int(cols/colwidth)
|
||||||
|
trytable = col_split(lsoutput, trycols)
|
||||||
|
works = True
|
||||||
|
for row in trytable:
|
||||||
|
row_break = False
|
||||||
|
for item in row:
|
||||||
|
if len(item) > colwidth - 1:
|
||||||
|
works, row_break = False, True
|
||||||
|
break
|
||||||
|
if row_break: break
|
||||||
|
if works: break
|
||||||
|
rowwidths = row_widths(trytable)
|
||||||
|
trytablecol = col_split(lscoloutput, len(trytable[0]))
|
||||||
|
for r in range(len(trytable)):
|
||||||
|
for c in range(len(trytable[r])):
|
||||||
|
padding = rowwidths[c] - len(trytable[r][c])
|
||||||
|
print >>output, trytablecol[r][c], "".ljust(padding),
|
||||||
|
print >>output
|
||||||
|
print >>output
|
||||||
|
listing = output.getvalue().rstrip()+ "\n"
|
||||||
|
output.close()
|
||||||
|
return listing
|
||||||
|
|
||||||
|
def main():
|
||||||
|
term = TerminalController()
|
||||||
|
cols = term.COLS
|
||||||
|
|
||||||
|
parser = OptionParser(usage="usage: %prog command [options] args\n\ncommand is one of: info, ls, cp, cat or rm\n\n"+
|
||||||
|
"For help on a particular command: %prog command", version="libprs500 version: " + VERSION)
|
||||||
|
parser.add_option("--log-packets", help="print out packet stream to stdout", dest="log_packets", action="store_true", default=False)
|
||||||
|
parser.remove_option("-h")
|
||||||
|
parser.disable_interspersed_args() # Allow unrecognized options
|
||||||
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
|
if len(args) < 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
command = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
dev = PRS500Device(log_packets=options.log_packets)
|
||||||
|
if command == "ls":
|
||||||
|
parser = OptionParser(usage="usage: %prog ls [options] path\n\npath must begin with /,a:/ or b:/")
|
||||||
|
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
||||||
|
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time unless other times are selected)", dest="ll", action="store_true", default=False)
|
||||||
|
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
||||||
|
parser.remove_option("-h")
|
||||||
|
parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False)
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) < 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
dev.open()
|
||||||
|
try:
|
||||||
|
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||||
|
except ArgumentError, e:
|
||||||
|
print >> sys.stderr, e
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
dev.close()
|
||||||
|
elif command == "info":
|
||||||
|
dev.open()
|
||||||
|
try:
|
||||||
|
info(dev)
|
||||||
|
finally: dev.close()
|
||||||
|
elif command == "cp":
|
||||||
|
parser = OptionParser(usage="usage: %prog cp [options] source destination\n\nsource is a path on the device and must begin with /,a:/ or b:/"+
|
||||||
|
"\n\ndestination is a path on your computer and can point to either a file or a directory")
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) < 2:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
if args[0].endswith("/"): path = args[0][:-1]
|
||||||
|
else: path = args[0]
|
||||||
|
outfile = args[1]
|
||||||
|
if os.path.isdir(outfile):
|
||||||
|
outfile = os.path.join(outfile, path[path.rfind("/")+1:])
|
||||||
|
outfile = open(outfile, "w")
|
||||||
|
dev.open()
|
||||||
|
try:
|
||||||
|
dev.get_file(path, outfile)
|
||||||
|
except ArgumentError, e:
|
||||||
|
print >>sys.stderr, e
|
||||||
|
finally:
|
||||||
|
dev.close()
|
||||||
|
outfile.close()
|
||||||
|
elif command == "cat":
|
||||||
|
outfile = sys.stdout
|
||||||
|
parser = OptionParser(usage="usage: %prog cat path\n\npath should point to a file on the device and must begin with /,a:/ or b:/")
|
||||||
|
options, args = parser.parse_args(args)
|
||||||
|
if len(args) < 1:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
if args[0].endswith("/"): path = args[0][:-1]
|
||||||
|
else: path = args[0]
|
||||||
|
outfile = sys.stdout
|
||||||
|
dev.open()
|
||||||
|
try:
|
||||||
|
dev.get_file(path, outfile)
|
||||||
|
except ArgumentError, e:
|
||||||
|
print >>sys.stderr, e
|
||||||
|
finally:
|
||||||
|
dev.close()
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
20
setup.py
Normal file
20
setup.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from distutils.core import setup
|
||||||
|
from libprs500 import VERSION
|
||||||
|
|
||||||
|
setup(name='libprs500',
|
||||||
|
version=VERSION,
|
||||||
|
description='Library to interface with the Sony Portable Reader 500 over USB.',
|
||||||
|
long_description =
|
||||||
|
"""
|
||||||
|
libprs500 is library to interface with the Sony Portable Reader 500 over USB.
|
||||||
|
It provides methods to list the contents of the file system on the device as well as
|
||||||
|
copy files from and to the device. It also provides a command line interface via the script prs500.py.
|
||||||
|
""",
|
||||||
|
author='Kovid Goyal',
|
||||||
|
author_email='kovid@kovidgoyal.net',
|
||||||
|
provides=['libprs500'],
|
||||||
|
requires=['pyusb'],
|
||||||
|
packages = ['libprs500'],
|
||||||
|
scripts = ['scripts/prs500.py']
|
||||||
|
)
|
115
spike.pl
Executable file
115
spike.pl
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
#! /usr/bin/perl -w
|
||||||
|
|
||||||
|
sub ST_INIT { 0; }
|
||||||
|
sub ST_OUT { 1; }
|
||||||
|
sub ST_IN { 2; }
|
||||||
|
|
||||||
|
$state= ST_INIT;
|
||||||
|
$count= 0;
|
||||||
|
|
||||||
|
while (<>) {
|
||||||
|
$_= &trim($_);
|
||||||
|
|
||||||
|
if ( />>> URB \d+ going down >>>/ ) {
|
||||||
|
&dump(\%packet) if $count;
|
||||||
|
$state= ST_OUT;
|
||||||
|
$count++;
|
||||||
|
%packet= (
|
||||||
|
num => $count
|
||||||
|
);
|
||||||
|
next;
|
||||||
|
} elsif ( /<<< URB \d+ coming back <<</ ) {
|
||||||
|
$state= ST_IN;
|
||||||
|
next;
|
||||||
|
} elsif ( $state == ST_INIT ) {
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( /^-- URB_FUNCTION_CONTROL_TRANSFER/ ) {
|
||||||
|
$packet{pipe}= 'C';
|
||||||
|
} elsif ( /^-- URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER/ ) {
|
||||||
|
$packet{pipe}= 'B';
|
||||||
|
} elsif ( /^\s+([0-9a-f]{8}:)\s+(.*)/ ) {
|
||||||
|
my ($offset)= $1;
|
||||||
|
my ($data) = $2;
|
||||||
|
my ($dline);
|
||||||
|
|
||||||
|
unless ( exists $packet{direction} ) {
|
||||||
|
$packet{direction}= ( $state == ST_IN ) ? '<' : '>';
|
||||||
|
$packet{data}= [];
|
||||||
|
}
|
||||||
|
|
||||||
|
#$_= <>;
|
||||||
|
#$_= &trim($_);
|
||||||
|
|
||||||
|
$dline= sprintf("%s %s", $offset, &ascii_rep($data));
|
||||||
|
|
||||||
|
push (@{$packet{data}}, $dline);
|
||||||
|
} elsif ( /^\s+SetupPacket/ ) {
|
||||||
|
$_ = <>;
|
||||||
|
$packet{setup}= (split(/:\s+/))[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&dump(\%packet) if $count;
|
||||||
|
|
||||||
|
0;
|
||||||
|
|
||||||
|
sub dump {
|
||||||
|
my ($href)= @_;
|
||||||
|
|
||||||
|
printf("%06d\t%s", $href->{num}, $href->{pipe});
|
||||||
|
if ( $href->{pipe} eq 'C' ) {
|
||||||
|
printf("S %s", $href->{setup});
|
||||||
|
if ( exists $href->{direction} ) {
|
||||||
|
print "\n";
|
||||||
|
$line= shift(@{$href->{data}});
|
||||||
|
printf("\tC%s %s", $href->{direction}, $line);
|
||||||
|
}
|
||||||
|
} elsif ( $href->{pipe} eq 'B' ) {
|
||||||
|
if ( exists $href->{direction} ) {
|
||||||
|
$line= shift(@{$href->{data}});
|
||||||
|
printf("%s %s", $href->{direction}, $line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn "unknown pipe";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach $line (@{$href->{data}}) {
|
||||||
|
printf("\t %s", $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
print "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trim {
|
||||||
|
my ($line)= @_;
|
||||||
|
|
||||||
|
$line=~ s/
//g;
|
||||||
|
$line=~ s/^\d+\s+\d+\.\d+\s+//;
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ascii_rep {
|
||||||
|
my (@hexdata)= split(/\s+/, $_[0]);
|
||||||
|
my ($i)= 0;
|
||||||
|
my ($compact, $width);
|
||||||
|
my ($ascii, $byte);
|
||||||
|
|
||||||
|
foreach $byte (@hexdata) {
|
||||||
|
my ($dec)= hex($byte);
|
||||||
|
my ($abyte);
|
||||||
|
|
||||||
|
$compact.= $byte;
|
||||||
|
$compact.= ' ' if ($i%2);
|
||||||
|
$i++;
|
||||||
|
|
||||||
|
$ascii.= ( $dec > 31 && $dec < 127 ) ? sprintf("%c", $dec) :
|
||||||
|
'.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$width= 40-length($compact);
|
||||||
|
return sprintf("%s%s %s\n", $compact, ' 'x${width}, $ascii);
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user