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