Package libprs500 :: Module communicate
[hide private]
[frames] | no frames]

Source Code for Module libprs500.communicate

  1  ##    Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net 
  2  ##    This program is free software; you can redistribute it and/or modify 
  3  ##    it under the terms of the GNU General Public License as published by 
  4  ##    the Free Software Foundation; either version 2 of the License, or 
  5  ##    (at your option) any later version. 
  6  ## 
  7  ##    This program is distributed in the hope that it will be useful, 
  8  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 10  ##    GNU General Public License for more details. 
 11  ## 
 12  ##    You should have received a copy of the GNU General Public License along 
 13  ##    with this program; if not, write to the Free Software Foundation, Inc., 
 14  ##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 15   
 16  ### End point description for PRS-500 procductId=667 
 17  ### Endpoint Descriptor: 
 18  ###        bLength                 7 
 19  ###        bDescriptorType         5 
 20  ###        bEndpointAddress     0x81  EP 1 IN 
 21  ###        bmAttributes            2 
 22  ###          Transfer Type            Bulk 
 23  ###          Synch Type               None 
 24  ###          Usage Type               Data 
 25  ###        wMaxPacketSize     0x0040  1x 64 bytes 
 26  ###        bInterval               0 
 27  ###      Endpoint Descriptor: 
 28  ###        bLength                 7 
 29  ###        bDescriptorType         5 
 30  ###        bEndpointAddress     0x02  EP 2 OUT 
 31  ###        bmAttributes            2 
 32  ###          Transfer Type            Bulk 
 33  ###          Synch Type               None 
 34  ###          Usage Type               Data 
 35  ###        wMaxPacketSize     0x0040  1x 64 bytes 
 36  ###        bInterval               0 
 37  ###  
 38  ### 
 39  ###  Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers. 
 40  ###  Has two configurations 1 is the USB charging config 2 is the self-powered config.  
 41  ###  I think config management is automatic. Endpoints are the same 
 42  """ 
 43  Contains the logic for communication with the device (a SONY PRS-500). 
 44   
 45  The public interface of class L{PRS500Device} defines the methods for performing various tasks.  
 46  """ 
 47  import usb, sys, os, time 
 48  from array import array 
 49   
 50  from prstypes import * 
 51  from errors import * 
 52   
 53  MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output 
 54  _packet_number = 0     #: Keep track of the packet number of packet tracing 
 55  KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] #: Protocol versions libprs500 has been tested with 
 56   
57 -def _log_packet(packet, header, stream=sys.stderr):
58 """ Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """ 59 global _packet_number 60 _packet_number += 1 61 print >>stream, str(_packet_number), header, "Type:", packet.__class__.__name__ 62 print >>stream, packet 63 print >>stream, "--"
64
65 -class File(object):
66 """ Wrapper that allows easy access to all information about files/directories """
67 - def __init__(self, file):
68 self.is_dir = file[1].is_dir #: True if self is a directory 69 self.is_readonly = file[1].is_readonly #: True if self is readonly 70 self.size = file[1].file_size #: Size in bytes of self 71 self.ctime = file[1].ctime #: Creation time of self as a epoch 72 self.wtime = file[1].wtime #: Creation time of self as an epoch 73 path = file[0] 74 if path.endswith("/"): path = path[:-1] 75 self.path = path #: Path to self 76 self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
77
78 - def __repr__(self):
79 """ Return path to self """ 80 return "File:"+self.path
81
82 - def __str__(self):
83 return self.name
84 85
86 -class DeviceDescriptor:
87 """ 88 Describes a USB device. 89 90 A description is composed of the Vendor Id, Product Id and Interface Id. 91 See the U{USB spec<http://www.usb.org/developers/docs/usb_20_05122006.zip>} 92 """ 93
94 - def __init__(self, vendor_id, product_id, interface_id) :
95 self.vendor_id = vendor_id 96 self.product_id = product_id 97 self.interface_id = interface_id
98
99 - def getDevice(self) :
100 """ 101 Return the device corresponding to the device descriptor if it is 102 available on a USB bus. Otherwise, return None. Note that the 103 returned device has yet to be claimed or opened. 104 """ 105 buses = usb.busses() 106 for bus in buses : 107 for device in bus.devices : 108 if device.idVendor == self.vendor_id : 109 if device.idProduct == self.product_id : 110 return device 111 return None
112 113
114 -class PRS500Device(object):
115 116 """ 117 Contains the logic for performing various tasks on the reader. 118 119 The implemented tasks are: 120 0. Getting information about the device 121 1. Getting a file from the device 122 2. Listing of directories. See the C{list} method. 123 """ 124 125 SONY_VENDOR_ID = 0x054c #: SONY Vendor Id 126 PRS500_PRODUCT_ID = 0x029b #: Product Id for the PRS-500 127 PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device 128 PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads 129 PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes 130
131 - def safe(func):
132 """ 133 Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly. 134 135 As a convenience, C{safe} automatically sends the a L{USBConnect} after calling func, unless func has 136 a keyword argument named C{end_session} set to C{False}. 137 138 An L{ArgumentError} will cause the L{USBConnect} command to be sent to the device, unless end_session is set to C{False}. 139 An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}. 140 """ 141 def run_session(*args, **kwargs): 142 dev = args[0] 143 res = None 144 try: 145 res = func(*args, **kwargs) 146 except ArgumentError, e: 147 if not kwargs.has_key("end_session") or kwargs["end_session"]: 148 dev._send_validated_command(USBConnect()) 149 raise e 150 except usb.USBError, e: 151 dev.close() 152 raise e 153 if not kwargs.has_key("end_session") or kwargs["end_session"]: 154 dev._send_validated_command(USBConnect()) 155 return res
156 157 return run_session
158
159 - def __init__(self, log_packets=False) :
160 """ @param log_packets: If true the packet stream to/from the device is logged """ 161 self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID, 162 PRS500Device.PRS500_PRODUCT_ID, 163 PRS500Device.PRS500_INTERFACE_ID) 164 self.device = self.device_descriptor.getDevice() 165 self.handle = None 166 self._log_packets = log_packets
167 168 @classmethod
169 - def _validate_response(cls, res, type=0x00, number=0x00):
170 """ Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """ 171 if type != res.type or number != res.rnumber: 172 raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+ 173 "\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
174
175 - def open(self) :
176 """ 177 Claim an interface on the device for communication. Requires write privileges to the device file. 178 Also initialize the device. See the source code for the sequenceof initialization commands. 179 180 @todo: Implement unlocking of the device 181 @todo: Check this on Mac OSX 182 """ 183 self.device = self.device_descriptor.getDevice() 184 if not self.device: 185 print >> sys.stderr, "Unable to find Sony Reader. Is it connected?" 186 sys.exit(1) 187 self.handle = self.device.open() 188 if sys.platform == 'darwin' : 189 # For some reason, Mac OS X doesn't set the 190 # configuration automatically like Linux does. 191 self.handle.setConfiguration(1) 192 self.handle.claimInterface(self.device_descriptor.interface_id) 193 self.handle.reset() 194 res = self._send_validated_command(GetUSBProtocolVersion()) 195 if res.code != 0: raise ProtocolError("Unable to get USB Protocol version.") 196 version = self._bulk_read(24, data_type=USBProtocolVersion)[0].version 197 if version not in KNOWN_USB_PROTOCOL_VERSIONS: 198 print >>sys.stderr, "WARNING: Usb protocol version " + hex(version) + " is unknown" 199 res = self._send_validated_command(SetBulkSize(size=0x028000)) 200 if res.code != 0: raise ProtocolError("Unable to set bulk size.") 201 self._send_validated_command(UnlockDevice(key=0x312d)) 202 if res.code != 0: 203 raise ProtocolError("Unlocking of device not implemented. Remove locking and retry.")
204 205
206 - def close(self):
207 """ Release device interface """ 208 self.handle.releaseInterface() 209 self.handle, self.device = None, None 210
211 - def _send_command(self, command, response_type=Response, timeout=100):
212 """ 213 Send L{command<Command>} to device and return its L{response<Response>}. 214 215 @param command: an object of type Command or one of its derived classes 216 @param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type. 217 @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. 218 """ 219 if self._log_packets: _log_packet(command, "Command") 220 bytes_sent = self.handle.controlMsg(0x40, 0x80, command) 221 if bytes_sent != len(command): 222 raise ControlError(desc="Could not send control request to device\n" + str(query.query)) 223 response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout)) 224 if self._log_packets: _log_packet(response, "Response") 225 return response
226
227 - def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=100):
228 """ 229 Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or command.number if cnumber==None}. Also check that 230 C{Response.type == Command.type}. 231 """ 232 if cnumber == None: cnumber = command.number 233 res = self._send_command(command, response_type=response_type, timeout=timeout) 234 PRS500Device._validate_response(res, type=command.type, number=cnumber) 235 return res
236
237 - def _bulk_write(self, data, packet_size=0x1000):
238 """ 239 Send data to device via a bulk transfer. 240 @type data: Any listable type supporting __getslice__ 241 @param packet_size: Size of packets to be sent to device. C{data} is broken up into packets to be sent to device. 242 """ 243 def bulk_write_packet(packet): 244 self.handle.bulkWrite(PRS500Device.PRS500_BULK_OUT_EP, packet) 245 if self._log_packets: _log_packet(Answer(packet), "Answer h->d")
246 247 bytes_left = len(data) 248 if bytes_left + 16 <= packet_size: 249 packet_size = bytes_left +16 250 first_packet = Answer(bytes_left+16) 251 first_packet[16:] = data 252 first_packet.length = len(data) 253 else: 254 first_packet = Answer(packet_size) 255 first_packet[16:] = data[0:packet_size-16] 256 first_packet.length = packet_size-16 257 first_packet.number = 0x10005 258 bulk_write_packet(first_packet) 259 pos = first_packet.length 260 bytes_left -= first_packet.length 261 while bytes_left > 0: 262 endpos = pos + packet_size if pos + packet_size <= len(data) else len(data) 263 bulk_write_packet(data[pos:endpos]) 264 bytes_left -= endpos - pos 265 pos = endpos 266 res = Response(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=5000)) 267 if self._log_packets: _log_packet(res, "Response") 268 if res.rnumber != 0x10005 or res.code != 0: 269 raise ProtocolError("Sending via Bulk Transfer failed with response:\n"+str(res)) 270 if res.data_size != len(data): 271 raise ProtocolError("Unable to transfer all data to device. Response packet:\n"+str(res)) 272 273
274 - def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
275 """ 276 Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size} 277 @param data_type: an object of type type. The data packet is returned as an object of type C{data_type}. 278 @return: A list of packets read from the device. Each packet is of type data_type 279 """ 280 def bulk_read_packet(data_type=Answer, size=0x1000): 281 data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size)) 282 if self._log_packets: _log_packet(data, "Answer d->h") 283 return data
284 285 bytes_left = bytes 286 packets = [] 287 while bytes_left > 0: 288 if packet_size > bytes_left: packet_size = bytes_left 289 packet = bulk_read_packet(data_type=data_type, size=packet_size) 290 bytes_left -= len(packet) 291 packets.append(packet) 292 self._send_validated_command(AcknowledgeBulkRead(packets[0].number), cnumber=command_number) 293 return packets 294 295 @safe
296 - def get_device_information(self, end_session=True):
297 """ 298 Ask device for device information. See L{DeviceInfoQuery}. 299 @return: (device name, device version, software version on device, mime type) 300 """ 301 size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16 302 data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0] 303 return (data.device_name, data.device_version, data.software_version, data.mime_type)
304 305 @safe
306 - def path_properties(self, path, end_session=True):
307 """ Send command asking device for properties of C{path}. Return L{FileProperties}. """ 308 res = self._send_validated_command(PathQuery(path), response_type=ListResponse) 309 data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0] 310 if path.endswith("/"): path = path[:-1] 311 if res.path_not_found : raise PathError(path + " does not exist on device") 312 if res.is_invalid : raise PathError(path + " is not a valid path") 313 if res.is_unmounted : raise PathError(path + " is not mounted") 314 if res.code not in (0, PathResponseCodes.IS_FILE): 315 raise PathError(path + " has an unknown error. Code: " + hex(res.code)) 316 return data
317 318 @safe
319 - def get_file(self, path, outfile, end_session=True):
320 """ 321 Read the file at path on the device and write it to outfile. For the logic see L{_get_file}. 322 323 The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen}, 324 L{FileRead} and L{FileClose} for details on the command packets used. 325 326 @param outfile: file object like C{sys.stdout} or the result of an C{open} call 327 """ 328 if path.endswith("/"): path = path[:-1] # We only copy files 329 file = self.path_properties(path, end_session=False) 330 if file.is_dir: raise PathError("Cannot read as " + path + " is a directory") 331 bytes = file.file_size 332 res = self._send_validated_command(FileOpen(path)) 333 if res.code != 0: 334 raise PathError("Unable to open " + path + " for reading. Response code: " + hex(res.code)) 335 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id 336 bytes_left, chunk_size, pos = bytes, 0x8000, 0 337 while bytes_left > 0: 338 if chunk_size > bytes_left: chunk_size = bytes_left 339 res = self._send_validated_command(FileIO(id, pos, chunk_size)) 340 if res.code != 0: 341 self._send_validated_command(FileClose(id)) 342 raise ProtocolError("Error while reading from " + path + ". Response code: " + hex(res.code)) 343 packets = self._bulk_read(chunk_size+16, command_number=FileIO.RNUMBER, packet_size=4096) 344 try: 345 array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream 346 for i in range(1, len(packets)): 347 array('B', packets[i]).tofile(outfile) 348 except IOError, e: 349 self._send_validated_command(FileClose(id)) 350 raise ArgumentError("File get operation failed. Could not write to local location: " + str(e)) 351 bytes_left -= chunk_size 352 pos += chunk_size 353 self._send_validated_command(FileClose(id))
354 # Not going to check response code to see if close was successful as there's not much we can do if it wasnt 355 356 357 358 @safe
359 - def list(self, path, recurse=False, end_session=True):
360 """ 361 Return a listing of path. See the code for details. See L{DirOpen}, 362 L{DirRead} and L{DirClose} for details on the command packets used. 363 364 @type path: string 365 @param path: The path to list 366 @type recurse: boolean 367 @param recurse: If true do a recursive listing 368 @return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files<File>}. 369 The path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive 370 list, then the first element will be (C{path}, children), the next will be (child, its children) and so on. If it 371 is not recursive the length of the outermost list will be 1. 372 """ 373 def _list(path): # Do a non recursive listsing of path 374 if not path.endswith("/"): path += "/" # Initially assume path is a directory 375 files = [] 376 candidate = self.path_properties(path, end_session=False) 377 if not candidate.is_dir: 378 path = path[:-1] 379 data = self.path_properties(path, end_session=False) 380 files = [ File((path, data)) ] 381 else: 382 # Get query ID used to ask for next element in list 383 res = self._send_validated_command(DirOpen(path)) 384 if res.code != 0: 385 raise PathError("Unable to open directory " + path + " for reading. Response code: " + hex(res.code)) 386 id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id 387 # Create command asking for next element in list 388 next = DirRead(id) 389 items = [] 390 while True: 391 res = self._send_validated_command(next, response_type=ListResponse) 392 size = res.data_size + 16 393 data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0] 394 # path_not_found seems to happen if the usb server doesn't have the permissions to access the directory 395 if res.is_eol or res.path_not_found: break 396 elif res.code != 0: 397 raise ProtocolError("Unknown error occured while reading contents of directory " + path + ". Response code: " + haex(res.code)) 398 items.append(data.name) 399 self._send_validated_command(DirClose(id)) # Ignore res.code as we cant do anything if close fails 400 for item in items: 401 ipath = path + item 402 data = self.path_properties(ipath, end_session=False) 403 files.append( File( (ipath, data) ) ) 404 files.sort() 405 return files
406 407 files = _list(path) 408 dirs = [(path, files)] 409 410 for file in files: 411 if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")): 412 dirs[len(dirs):] = self.list(file.path, recurse=True, end_session=False) 413 return dirs 414 415 @safe
416 - def available_space(self, end_session=True):
417 """ 418 Get free space available on the mountpoints: 419 1. /Data/ Device memory 420 2. a:/ Memory Stick 421 3. b:/ SD Card 422 423 @return: A list of tuples. Each tuple has form ("location", free space, total space) 424 """ 425 data = [] 426 for path in ("/Data/", "a:/", "b:/"): 427 res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card 428 buffer_size = 16 + res.data[2] 429 pkt = self._bulk_read(buffer_size, data_type=FreeSpaceAnswer, command_number=FreeSpaceQuery.NUMBER)[0] 430 data.append( (path, pkt.free_space, pkt.total) ) 431 return data
432
433 - def _exists(self, path):
434 """ Return (True, FileProperties) if path exists or (False, None) otherwise """ 435 dest = None 436 try: 437 dest = self.path_properties(path, end_session=False) 438 except PathError, e: 439 if "does not exist" in str(e): return (False, None) 440 else: raise e 441 return (True, dest)
442 443 @safe
444 - def touch(self, path, end_session=True):
445 """ 446 Create a file at path 447 448 @todo: Update file modification time if it exists 449 """ 450 if path.endswith("/") and len(path) > 1: path = path[:-1] 451 exists, file = self._exists(path) 452 if exists and file.is_dir: 453 raise PathError("Cannot touch directories") 454 if not exists: 455 res = self._send_validated_command(FileCreate(path)) 456 if res.code != 0: 457 raise PathError("Could not create file " + path + ". Response code: " + str(hex(res.code)))
458 ## res = self._send_validated_command(SetFileInfo(path)) 459 ## if res.code != 0: 460 ## raise ProtocolError("Unable to touch " + path + ". Response code: " + hex(res.code)) 461 ## file.wtime = int(time.time()) 462 ## self._bulk_write(file[16:]) 463 464 465 @safe
466 - def put_file(self, infile, path, end_session=True):
467 exists, dest = self._exists(path) 468 if exists: 469 if not dest.is_dir: raise PathError("Cannot write to " + path + " as it already exists") 470 if not path.endswith("/"): path += "/" 471 path += os.path.basename(infile.name) 472 exists, dest = self._exists(path) 473 if exists: raise PathError("Cannot write to " + path + " as it already exists") 474 res = self._send_validated_command(FileCreate(path)) 475 if res.code != 0: 476 raise ProtocolError("There was an error creating device:"+path+". Response code: "+hex(res.code)) 477 chunk_size = 0x8000 478 data_left = True 479 res = self._send_validated_command(FileOpen(path, mode=FileOpen.WRITE)) 480 if res.code != 0: 481 raise ProtocolError("Unable to open " + path + " for writing. Response code: " + hex(res.code)) 482 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id 483 pos = 0 484 while data_left: 485 data = array('B') 486 try: 487 data.fromfile(infile, chunk_size) 488 except EOFError: 489 data_left = False 490 res = self._send_validated_command(FileIO(id, pos, len(data), mode=FileIO.WNUMBER)) 491 if res.code != 0: 492 raise ProtocolError("Unable to write to " + path + ". Response code: " + hex(res.code)) 493 self._bulk_write(data) 494 pos += len(data) 495 self._send_validated_command(FileClose(id)) # Ignore res.code as cant do anything if close fails 496 file = self.path_properties(path, end_session=False) 497 if file.file_size != pos: 498 raise ProtocolError("Copying to device failed. The file was truncated by " + str(data.file_size - pos) + " bytes")
499 500 @safe
501 - def del_file(self, path, end_session=True):
502 data = self.path_properties(path, end_session=False) 503 if data.is_dir: raise PathError("Cannot delete directories") 504 res = self._send_validated_command(FileDelete(path), response_type=ListResponse) 505 if res.code != 0: 506 raise ProtocolError("Unable to delete " + path + " with response:\n" + str(res))
507 508 @safe
509 - def mkdir(self, path, end_session=True):
510 if not path.endswith("/"): path += "/" 511 error_prefix = "Cannot create directory " + path 512 res = self._send_validated_command(DirCreate(path)).data[0] 513 if res == 0xffffffcc: 514 raise PathError(error_prefix + " as it already exists") 515 elif res == PathResponseCodes.NOT_FOUND: 516 raise PathError(error_prefix + " as " + path[0:path[:-1].rfind("/")] + " does not exist ") 517 elif res == PathResponseCodes.INVALID: 518 raise PathError(error_prefix + " as " + path + " is invalid") 519 elif res != 0: 520 raise PathError(error_prefix + ". Response code: " + hex(res))
521 522 @safe
523 - def rm(self, path, end_session=True):
524 """ Delete path from device if it is a file or an empty directory """ 525 dir = self.path_properties(path, end_session=False) 526 if not dir.is_dir: 527 self.del_file(path, end_session=False) 528 else: 529 if not path.endswith("/"): path += "/" 530 res = self._send_validated_command(DirDelete(path)) 531 if res.code == PathResponseCodes.HAS_CHILDREN: 532 raise PathError("Cannot delete directory " + path + " as it is not empty") 533 if res.code != 0: 534 raise ProtocolError("Failed to delete directory " + path + ". Response code: " + hex(res.code))
535 536 537 538 #dev = PRS500Device(log_packets=False) 539 #dev.open() 540 #print dev.get_file("/etc/sysctl.conf", sys.stdout) 541 #dev.close() 542