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 
 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   
56 -def _log_packet(packet, header, stream=sys.stderr):
57 """ Log C{packet} to stream C{stream}. Header should be a small word describing the type of packet. """ 58 global _packet_number 59 _packet_number += 1 60 print >>stream, header, "(Packet #", str(_packet_number) + ")\n" 61 print >>stream, packet 62 print >>stream, "--"
63
64 -class File(object):
65 """ Wrapper that allows easy access to all information about files/directories """
66 - def __init__(self, file):
67 self.is_dir = file[1].is_dir #: True if self is a directory 68 self.is_readonly = file[1].is_readonly #: True if self is readonly 69 self.size = file[1].file_size #: Size in bytes of self 70 self.ctime = file[1].ctime #: Creation time of self as a epoch 71 self.wtime = file[1].wtime #: Creation time of self as an epoch 72 path = file[0] 73 if path.endswith("/"): path = path[:-1] 74 self.path = path #: Path to self 75 self.name = path[path.rfind("/")+1:].rstrip() #: Name of self
76
77 - def __repr__(self):
78 """ Return path to self """ 79 return self.path
80 81
82 -class DeviceDescriptor:
83 """ 84 Describes a USB device. 85 86 A description is composed of the Vendor Id, Product Id and Interface Id. 87 See the U{USB spec<http://www.usb.org/developers/docs/usb_20_05122006.zip>} 88 """ 89
90 - def __init__(self, vendor_id, product_id, interface_id) :
91 self.vendor_id = vendor_id 92 self.product_id = product_id 93 self.interface_id = interface_id
94
95 - def getDevice(self) :
96 """ 97 Return the device corresponding to the device descriptor if it is 98 available on a USB bus. Otherwise, return None. Note that the 99 returned device has yet to be claimed or opened. 100 """ 101 buses = usb.busses() 102 for bus in buses : 103 for device in bus.devices : 104 if device.idVendor == self.vendor_id : 105 if device.idProduct == self.product_id : 106 return device 107 return None
108 109
110 -class PRS500Device(object):
111 112 """ 113 Contains the logic for performing various tasks on the reader. 114 115 The implemented tasks are: 116 0. Getting information about the device 117 1. Getting a file from the device 118 2. Listing of directories. See the C{list} method. 119 """ 120 121 SONY_VENDOR_ID = 0x054c #: SONY Vendor Id 122 PRS500_PRODUCT_ID = 0x029b #: Product Id for the PRS-500 123 PRS500_INTERFACE_ID = 0 #: The interface we use to talk to the device 124 PRS500_BULK_IN_EP = 0x81 #: Endpoint for Bulk reads 125 PRS500_BULK_OUT_EP = 0x02 #: Endpoint for Bulk writes 126
127 - def __init__(self, log_packets=False) :
128 """ @param log_packets: If true the packet stream to/from the device is logged """ 129 self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID, 130 PRS500Device.PRS500_PRODUCT_ID, 131 PRS500Device.PRS500_INTERFACE_ID) 132 self.device = self.device_descriptor.getDevice() 133 self.handle = None 134 self._log_packets = log_packets
135 136 @classmethod
137 - def _validate_response(cls, res, type=0x00, number=0x00):
138 """ Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """ 139 if type != res.type or number != res.rnumber: 140 raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+ 141 "\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
142
143 - def open(self) :
144 """ 145 Claim an interface on the device for communication. Requires write privileges to the device file. 146 147 @todo: Check this on Mac OSX 148 """ 149 self.device = self.device_descriptor.getDevice() 150 if not self.device: 151 print >> sys.stderr, "Unable to find Sony Reader. Is it connected?" 152 sys.exit(1) 153 self.handle = self.device.open() 154 if sys.platform == 'darwin' : 155 # XXX : For some reason, Mac OS X doesn't set the 156 # configuration automatically like Linux does. 157 self.handle.setConfiguration(1) # TODO: Check on Mac OSX 158 self.handle.claimInterface(self.device_descriptor.interface_id) 159 self.handle.reset()
160
161 - def close(self):
162 """ Release device interface """ 163 self.handle.releaseInterface() 164 self.handle, self.device = None, None
165
166 - def _send_command(self, command, response_type=Response, timeout=100):
167 """ 168 Send L{command<Command>} to device and return its L{response<Response>}. 169 170 @param command: an object of type Command or one of its derived classes 171 @param response_type: an object of type 'type'. The return packet from the device is returned as an object of type response_type. 172 @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. 173 """ 174 if self._log_packets: _log_packet(command, "Command") 175 bytes_sent = self.handle.controlMsg(0x40, 0x80, command) 176 if bytes_sent != len(command): 177 raise ControlError(desc="Could not send control request to device\n" + str(query.query)) 178 response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout)) 179 if self._log_packets: _log_packet(response, "Response") 180 return response
181
182 - def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=100):
183 """ 184 Wrapper around L{_send_command} that checks if the C{Response.rnumber == cnumber or command.number if cnumber==None}. Also check that 185 C{Response.type == Command.type}. 186 """ 187 if cnumber == None: cnumber = command.number 188 res = self._send_command(command, response_type=response_type, timeout=timeout) 189 PRS500Device._validate_response(res, type=command.type, number=cnumber) 190 return res
191
192 - def _bulk_read_packet(self, data_type=Answer, size=4096):
193 """ 194 Read in a data packet via a Bulk Read. 195 196 @param data_type: an object of type type. The data packet is returned as an object of type C{data_type}. 197 @param size: the expected size of the data packet. 198 """ 199 data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size)) 200 if self._log_packets: _log_packet(data, "Answer d->h") 201 return data
202
203 - def _bulk_read(self, bytes, command_number=0x00, packet_size=4096, data_type=Answer):
204 """ Read in C{bytes} bytes via a bulk transfer in packets of size S{<=} C{packet_size} """ 205 bytes_left = bytes 206 packets = [] 207 while bytes_left > 0: 208 if packet_size > bytes_left: packet_size = bytes_left 209 packet = self._bulk_read_packet(data_type=data_type, size=packet_size) 210 bytes_left -= len(packet) 211 packets.append(packet) 212 self._send_validated_command(AcknowledgeBulkRead(packets[0].id), cnumber=command_number) 213 return packets
214
215 - def _test_bulk_reads(self):
216 """ Carries out a test of bulk reading as part of session initialization. """ 217 self._send_validated_command( ShortCommand(number=0x00, type=0x01, command=0x00) ) 218 self._bulk_read(24, command_number=0x00)
219
220 - def _start_session(self):
221 """ 222 Send the initialization sequence to the device. See the code for details. 223 This method should be called before any real work is done. Though most things seem to work without it. 224 """ 225 self.handle.reset() 226 self._test_bulk_reads() 227 self._send_validated_command( ShortCommand(number=0x0107, command=0x028000, type=0x01) ) # TODO: Figure out the meaning of this command 228 self._test_bulk_reads() 229 self._send_validated_command( ShortCommand(number=0x0106, type=0x01, command=0x312d) ) # TODO: Figure out the meaning of this command 230 self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x01) )
231
232 - def _end_session(self):
233 """ Send the end session command to the device. Causes the device to change status from "Do not disconnect" to "USB Connected" """ 234 self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x00) )
235
236 - def _run_session(self, *args):
237 """ 238 Wrapper that automatically calls L{_start_session} and L{_end_session}. 239 240 @param args: An array whose first element is the method to call and whose remaining arguments are passed to that mathos as an array. 241 """ 242 self._start_session() 243 res = None 244 try: 245 res = args[0](args[1:]) 246 except ArgumentError, e: 247 self._end_session() 248 raise e 249 self._end_session() 250 return res
251
252 - def _get_device_information(self, args):
253 """ Ask device for device information. See L{DeviceInfoQuery}. """ 254 size = self._send_validated_command(DeviceInfoQuery()).data[2] + 16 255 data = self._bulk_read(size, command_number=DeviceInfoQuery.NUMBER, data_type=DeviceInfo)[0] 256 return (data.device_name, data.device_version, data.software_version, data.mime_type)
257
258 - def get_device_information(self):
259 """ Return (device name, device version, software version on device, mime type). See L{_get_device_information} """ 260 return self._run_session(self._get_device_information)
261
262 - def _get_path_properties(self, path):
263 """ Send command asking device for properties of C{path}. Return (L{Response}, L{Answer}). """ 264 res = self._send_validated_command(PathQuery(path), response_type=ListResponse) 265 data = self._bulk_read(0x28, data_type=FileProperties, command_number=PathQuery.NUMBER)[0] 266 if path.endswith("/"): path = path[:-1] 267 if res.path_not_found : raise PathError(path + " does not exist on device") 268 if res.is_invalid : raise PathError(path + " is not a valid path") 269 if res.is_unmounted : raise PathError(path + " is not mounted") 270 return (res, data)
271
272 - def get_file(self, path, outfile):
273 """ 274 Read the file at path on the device and write it to outfile. For the logic see L{_get_file}. 275 276 @param outfile: file object like C{sys.stdout} or the result of an C{open} call 277 """ 278 self._run_session(self._get_file, path, outfile)
279
280 - def _get_file(self, args):
281 """ 282 Fetch a file from the device and write it to an output stream. 283 284 The data is fetched in chunks of size S{<=} 32K. Each chunk is make of packets of size S{<=} 4K. See L{FileOpen}, 285 L{FileRead} and L{FileClose} for details on the command packets used. 286 287 @param args: C{path, outfile = arg[0], arg[1]} 288 """ 289 path, outfile = args[0], args[1] 290 if path.endswith("/"): path = path[:-1] # We only copy files 291 res, data = self._get_path_properties(path) 292 if data.is_dir: raise PathError("Cannot read as " + path + " is a directory") 293 bytes = data.file_size 294 self._send_validated_command(FileOpen(path)) 295 id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id 296 bytes_left, chunk_size, pos = bytes, 0x8000, 0 297 while bytes_left > 0: 298 if chunk_size > bytes_left: chunk_size = bytes_left 299 res = self._send_validated_command(FileRead(id, pos, chunk_size)) 300 packets = self._bulk_read(chunk_size+16, command_number=FileRead.NUMBER, packet_size=4096) 301 try: 302 array('B', packets[0][16:]).tofile(outfile) # The first 16 bytes are meta information on the packet stream 303 for i in range(1, len(packets)): 304 array('B', packets[i]).tofile(outfile) 305 except IOError, e: 306 self._send_validated_command(FileClose(id)) 307 raise ArgumentError("File get operation failed. Could not write to local location: " + str(e)) 308 bytes_left -= chunk_size 309 pos += chunk_size 310 self._send_validated_command(FileClose(id))
311 312
313 - def _list(self, args):
314 """ 315 Ask the device to list a path. See the code for details. See L{DirOpen}, 316 L{DirRead} and L{DirClose} for details on the command packets used. 317 318 @param args: C{path=args[0]} 319 @return: A list of tuples. The first element of each tuple is a string, the path. The second is a L{FileProperties}. 320 If the path points to a file, the list will have length 1. 321 """ 322 path = args[0] 323 if not path.endswith("/"): path += "/" # Initially assume path is a directory 324 files = [] 325 res, data = self._get_path_properties(path) 326 if res.is_file: 327 path = path[:-1] 328 res, data = self._get_path_properties(path) 329 files = [ (path, data) ] 330 else: 331 # Get query ID used to ask for next element in list 332 self._send_validated_command(DirOpen(path), response_type=ListResponse) 333 id = self._bulk_read(0x14, data_type=IdAnswer, command_number=DirOpen.NUMBER)[0].id 334 # Create command asking for next element in list 335 next = DirRead(id) 336 items = [] 337 while True: 338 res = self._send_validated_command(next, response_type=ListResponse) 339 size = res.data[2] + 16 340 data = self._bulk_read(size, data_type=ListAnswer, command_number=DirRead.NUMBER)[0] 341 # path_not_found seems to happen if the usb server doesn't have the permissions to access the directory 342 if res.is_eol or res.path_not_found: break 343 items.append(data.name) 344 self._send_validated_command(DirClose(id)) 345 for item in items: 346 ipath = path + item 347 res, data = self._get_path_properties(ipath) 348 files.append( (ipath, data) ) 349 files.sort() 350 return files
351
352 - def list(self, path, recurse=False):
353 """ 354 Return a listing of path. 355 356 See L{_list} for the communication logic. 357 358 @type path: string 359 @param path: The path to list 360 @type recurse: boolean 361 @param recurse: If true do a recursive listing 362 @return: A list of tuples. The first element of each tuple is a path. The second element is a list of L{Files<File>}. 363 The path is the path we are listing, the C{Files} are the files/directories in that path. If it is a recursive 364 list, then the first element will be (C{path}, children), the next will be (child, its children) and so on. 365 """ 366 files = self._run_session(self._list, path) 367 files = [ File(file) for file in files ] 368 dirs = [(path, files)] 369 for file in files: 370 if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")): 371 dirs[len(dirs):] = self.list(file.path, recurse=True) 372 return dirs
373
374 - def available_space(self):
375 """ 376 Get free space available on the mountpoints: 377 1. /Data/ Device memory 378 2. a:/ Memory Stick 379 3. b:/ SD Card 380 381 @return: A list of tuples. Each tuple has form ("location", free space, total space) 382 """ 383 return self._run_session(self._available_space)
384
385 - def _available_space(self, args):
386 """ L{available_space} """ 387 data = [] 388 for path in ("/Data/", "a:/", "b:/"): 389 res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card 390 buffer_size = 16 + res.data[2] 391 pkt = self._bulk_read(buffer_size, data_type=FreeSpaceAnswer, command_number=FreeSpaceQuery.NUMBER)[0] 392 data.append( (path, pkt.free_space, pkt.total) ) 393 return data
394