Package phones :: Module com_toshibavm4050
[hide private]
[frames] | no frames]

Source Code for Module phones.com_toshibavm4050

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2005 Simon Capper <scapper@sbcglobal.net> 
  4  ### 
  5  ### This program is free software; you can redistribute it and/or modify 
  6  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
  7  ### 
  8  ### $Id: com_toshibavm4050.py 4516 2007-12-21 22:00:57Z djpham $ 
  9   
 10  """Communicate with the toshiba CDM 8900 cell phone""" 
 11   
 12  # standard modules 
 13  import sha 
 14  import re 
 15  import time 
 16   
 17  # our modules 
 18  import common 
 19  import com_phone 
 20  import com_brew 
 21  import prototypes 
 22  import commport 
 23  import p_brew 
 24  import p_toshibavm4050 
 25  import helpids 
26 27 -class Phone(com_phone.Phone, com_brew.BrewProtocol):
28 "Talk to Toshiba VM4050 cell phone" 29 30 desc="Toshiba VM4050" 31 helpid=helpids.ID_PHONE_TOSHIBAVM4050 32 protocolclass=p_toshibavm4050 33 serialsname='toshibavm4050' 34 35 builtinimages=() 36 37 builtinringtones=( 'Common', 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5', 38 'Ring 6', 'Ring 7', 'Ring 8', 'Ring 9', 39 'Alarm 1', 'Alarm 2', 'Alarm 3', 'Alarm 4', 'Alarm 5', 40 'Alarm 6', 'Alarm 7', 'Alarm 8', 'Alarm 9', 41 'Stars & Stripes', 'When the Saints', 'Bach', 42 'William Tell', 'Nachtmusik', 'Str Serenade', 'Toccata', 43 'Meistersinger', 'Blue Danube') 44
45 - def __init__(self, logtarget, commport):
46 "Calls all the constructors and sets initial modes" 47 com_phone.Phone.__init__(self, logtarget, commport) 48 com_brew.BrewProtocol.__init__(self) 49 self.log("Attempting to contact phone") 50 self.mode=self.MODENONE
51
52 - def getfundamentals(self, results):
53 """Gets information fundamental to interopating with the phone and UI. 54 55 Currently this is: 56 57 - 'uniqueserial' a unique serial number representing the phone 58 - 'groups' the phonebook groups 59 - 'wallpaper-index' map index numbers to names 60 - 'ringtone-index' map index numbers to ringtone names 61 62 This method is called before we read the phonebook data or before we 63 write phonebook data. 64 """ 65 self.log("Getting fundamentals") 66 # use a hash of ESN and other stuff (being paranoid) 67 self.log("Retrieving fundamental phone information") 68 self.setmode(self.MODEBREW) 69 self.log("Phone serial number") 70 results['uniqueserial']=sha.new(self.get_esn()).hexdigest() 71 # This phone does not support groups 72 results['groups']={} 73 #self.getwallpaperindices(results) 74 self.getringtoneindices(results) 75 self.log("Fundamentals retrieved") 76 return results
77
78 - def getmediaindex(self, builtins, results, key):
79 """Gets the media (wallpaper/ringtone) index 80 81 @param builtins: the builtin list on the phone 82 @param results: places results in this dict 83 @param maps: the list of index files and locations 84 @param key: key to place results in 85 """ 86 87 self.log("Reading "+key) 88 media={} 89 90 # builtins 91 c=1 92 for name in builtins: 93 if name: 94 media[c]={'name': name, 'origin': 'builtin' } 95 c+=1 96 97 results[key]=media 98 return media
99
100 - def getwallpaperindices(self, results):
101 return self.getmediaindex(self.builtinimages, results, 'wallpaper-index')
102
103 - def getringtoneindices(self, results):
104 return self.getmediaindex(self.builtinringtones, results, 'ringtone-index')
105
106 - def listsubdirs(self, dir='', recurse=0):
107 self.log("Getting file system in dir '"+dir+"'") 108 res=com_brew.BrewProtocol.listsubdirs(self, dir, recurse) 109 # toshiba is bad, they don't list these directories when you query the phone 110 # so we add them in ourselves 111 if not len(dir): 112 res['C']={ 'name': 'C', 'type': 'directory' } 113 res['D']={ 'name': 'D', 'type': 'directory' } 114 res['G']={ 'name': 'G', 'type': 'directory' } 115 return res
116
117 - def enable_data_transfer(self):
122
123 - def disable_data_transfer(self):
126
127 - def readdatarecord(self):
128 return self.getfilecontents("D/APL/SWAP/DIAG_APSWP_MEMORY")
129
130 - def writedatarecord(self, record):
131 return self.writefile("D/APL/SWAP/DIAG_APSWP_MEMORY", record)
132
133 - def get_esn(self, data=None):
134 # return the ESN of this phone 135 return self.get_brew_esn()
136
137 - def getcalendar(self, result):
138 raise NotImplementedError()
139
140 - def getwallpapers(self, result):
141 raise NotImplementedError()
142
143 - def getringtones(self, result):
144 raise NotImplementedError()
145
146 - def getphonebook(self, result):
147 """Reads the phonebook data. The L{getfundamentals} information will 148 already be in result.""" 149 pbook={} 150 self.enable_data_transfer() 151 # no way to read the number of entries, just have to read all 152 count=0 153 # try: 154 for i in range(self.protocolclass.NUMSLOTS): 155 entry=self.protocolclass.pbentry() 156 self.progress(i, self.protocolclass.NUMSLOTS, "") 157 req=self.protocolclass.tosh_getpbentryrequest() 158 req.entry_index=i 159 res=self.sendpbcommand(req, self.protocolclass.tosh_getpbentryresponse) 160 if not res.swap_ok: 161 raw=self.readdatarecord() 162 if __debug__: 163 open("c:/projects/temp/record_in"+`i`, "wb").write(raw) 164 buf=prototypes.buffer(raw) 165 entry.readfrombuffer(buf, logtitle="phonebook data record") 166 else: 167 continue 168 self.log("Read entry "+`i`+" - "+entry.name) 169 pb_entry=self.extractphonebookentry(entry, result) 170 pbook[i]=pb_entry 171 #except Exception, e: 172 # must disable this to prevent phone problems 173 # self.disable_data_transfer() 174 # raise Exception, e 175 self.disable_data_transfer() 176 self.progress(self.protocolclass.NUMSLOTS, self.protocolclass.NUMSLOTS, "Phone book read completed") 177 result['phonebook']=pbook 178 return result
179 180 _type_map={ 181 0: 'phone', 182 1: 'home', 183 2: 'office', 184 3: 'cell', 185 4: 'pager', 186 5: 'fax', 187 } 188 189 _ringer_pattern1_map={ 190 0x0100: 'Ring 1', 191 0x0101: 'Ring 2', 192 0x0102: 'Ring 3', 193 0x0103: 'Ring 4', 194 0x0104: 'Ring 5', 195 0x0105: 'Ring 6', 196 0x0106: 'Ring 7', 197 0x0107: 'Ring 8', 198 0x0108: 'Ring 9', 199 } 200 201 _ringer_pattern2_map={ 202 0x0009: 'Alarm 1', 203 0x000A: 'Alarm 2', 204 0x000B: 'Alarm 3', 205 0x000C: 'Alarm 4', 206 0x000D: 'Alarm 5', 207 0x000E: 'Alarm 6', 208 0x000F: 'Alarm 7', 209 0x0010: 'Alarm 8', 210 0x0011: 'Alarm 9', 211 } 212 213 _ringer_melody_map={ 214 0x0000: 'Stars & Stripes', 215 0x0001: 'When the Saints', 216 0x0002: 'Bach', 217 0x0003: 'William Tell', 218 0x0004: 'Nachtmusik', 219 0x0005: 'Str Serenade', 220 0x0006: 'Toccata', 221 0x0007: 'Meistersinger', 222 0x0008: 'Blue Danube', 223 } 224
225 - def _get_ringtone(self, ringer_group, ringer_index):
226 # no idea what values downloaded ringtones use 227 # exception handler will take care of any wierd 228 # value we do not understand 229 try: 230 if ringer_group==1: 231 return self._ringer_pattern1_map[ringer_index] 232 if ringer_group==2: 233 return self._ringer_pattern2_map[ringer_index] 234 if ringer_group==3: 235 return self._ringer_melody_map[ringer_index] 236 except: 237 pass 238 return 'Common'
239
240 - def __find_index(self, map, ringer):
241 for k, v in map.iteritems(): 242 if v==ringer: 243 return k 244 return -1
245
246 - def _getringtone_group_index(self, ringtone):
247 ringer_index = self.__find_index(self._ringer_pattern1_map,ringtone) 248 if ringer_index != -1: 249 return 1, ringer_index 250 ringer_index = self.__find_index(self._ringer_pattern2_map,ringtone) 251 if ringer_index != -1: 252 return 2, ringer_index 253 ringer_index = self.__find_index(self._ringer_melody_map,ringtone) 254 if ringer_index != -1: 255 return 3, ringer_index 256 return 5, 0
257
258 - def extractphonebookentry(self, entry, result):
259 """Return a phonebook entry in BitPim format. This is called from getphonebook.""" 260 res={} 261 # serials 262 res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.slot, 263 'sourceuniqueid': result['uniqueserial']} ] 264 # numbers 265 set=False 266 secret=False 267 ringtone='Common' 268 numbers=[] 269 for i in range(self.protocolclass.MAXPHONENUMBERS): 270 if entry.numbers[i].valid and len(entry.numbers[i].number): 271 type=self._type_map[entry.numbers[i].type] 272 numbers.append( {'number': entry.numbers[i].number, 'type': type} ) 273 # secret and ringtone are per number on this phone, bitpim only supports one 274 # per phonebook entry, so we set the phone entry to secret if any number is secret 275 # and we pick the first ringtone that is not the default and apply to the whole entry 276 if not secret and entry.numbers[i].secret: 277 secret=True 278 if ringtone=='Common' and entry.numbers[i].ringer_group!=5: 279 ringtone=self._get_ringtone(entry.numbers[i].ringer_group, entry.numbers[i].ringer_index) 280 if len(numbers): 281 res['numbers']=numbers 282 if secret: 283 res['flags']=[{'secret': True}] 284 res['ringtones']=[{'ringtone': ringtone, 'use': 'call'}] 285 # name 286 if len(entry.name): # yes, the toshiba can have a blank name! 287 res['names']=[{'full': entry.name}] 288 # emails (we treat wireless as email addr) 289 emails=[] 290 for i in range(self.protocolclass.MAXEMAILS): 291 if entry.emails[i].valid and len(entry.emails[i].email): 292 emails.append( {'email': entry.emails[i].email} ) 293 if len(emails): 294 res['emails']=emails 295 # urls 296 if len(entry.web_page): 297 res['urls']=[ {'url': entry.web_page} ] 298 return res
299
300 - def makephonebookentry(self, fields):
301 e=self.protocolclass.pbentry() 302 # some defaults 303 secret=False 304 if fields['secret']!=None: 305 secret=fields['secret'] 306 ringtone='Common' 307 if fields['ringtone']!=None: 308 ringtone=fields['ringtone'] 309 ringer_group, ringer_index=self._getringtone_group_index(ringtone) 310 if fields['name']: 311 e.name=fields['name'] 312 else: 313 e.name='' 314 for i in range(len(fields['numbers'])): 315 n=self.protocolclass.pbnumber() 316 n.valid=1 317 n.number=fields['numbers'][i] 318 n.type=fields['numbertypes'][i] 319 n.secret=secret 320 n.ringer_group=ringer_group 321 n.ringer_index=ringer_index 322 e.numbers.append(n) 323 for i in range(len(fields['emails'])): 324 n=self.protocolclass.pbemail() 325 n.valid=1 326 n.email=fields['emails'][i] 327 e.emails.append(n) 328 if fields['web_page']: 329 e.web_page=fields['web_page'] 330 e.slot=fields['slot'] 331 return e
332
333 - def savephonebook(self, data):
334 # brute force, it is faster, otherwise we would have to 335 # examine each entry to see if it was there and then decide what 336 # to do with it, deleting and re-writing is faster and what the user 337 # seleted with "OVERWRITE" 338 self.log("New phonebook\n"+common.prettyprintdict(data['phonebook'])) 339 pb=data['phonebook'] 340 keys=pb.keys() 341 keys.sort() 342 keys=keys[:self.protocolclass.NUMSLOTS] 343 self.enable_data_transfer() 344 try: 345 # delete the old phonebook 346 self.rmfile("D/APL/SWAP/DIAG_APSWP_MEMORY") 347 for i in range(self.protocolclass.NUMSLOTS): 348 self.deletepbentry(i) 349 # create the new phonebook 350 for i in range(len(keys)): 351 slot=keys[i] 352 entry=self.makephonebookentry(pb[slot]) 353 self.progress(i, len(keys), "Writing "+entry.name) 354 self.log('Writing entry '+`slot`+" - "+entry.name) 355 self.sendpbentrytophone(entry) 356 except Exception, e: 357 # must disable this to prevent phone problems 358 self.disable_data_transfer() 359 raise Exception, e 360 self.disable_data_transfer() 361 self.progress(len(keys)+1, len(keys)+1, "Phone book write completed") 362 return data
363
364 - def sendpbentrytophone(self, entry):
365 # write out the new one 366 buf=prototypes.buffer() 367 entry.writetobuffer(buf, logtitle="Sending pbentrytophone") 368 data=buf.getvalue() 369 self.writedatarecord(data) 370 req=self.protocolclass.tosh_setpbentryrequest() 371 req.entry_index=entry.slot 372 if __debug__: 373 open("c:/projects/temp/record_out"+`entry.slot`, "wb").write(data) 374 res=self.sendpbcommand(req, self.protocolclass.tosh_setpbentryresponse) 375 if res.swap_ok: 376 self.log("Error writing phonebook entry")
377
378 - def deletepbentry(self, slot):
379 req=self.protocolclass.tosh_modifypbentryrequest() 380 req.entry_index=slot 381 res=self.sendpbcommand(req, self.protocolclass.tosh_modifypbentryresponse)
382
383 - def sendpbcommand(self, request, responseclass):
384 self.setmode(self.MODEBREW) 385 buffer=prototypes.buffer() 386 request.writetobuffer(buffer, logtitle="toshiba vm4050 phonebook request") 387 data=buffer.getvalue() 388 data=common.pppescape(data+common.crcs(data))+common.pppterminator 389 first=data[0] 390 try: 391 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 392 except com_phone.modeignoreerrortypes: 393 self.mode=self.MODENONE 394 self.raisecommsdnaexception("manipulating the phonebook") 395 self.comm.success=True 396 397 origdata=data 398 # sometimes there is junk at the begining, eg if the user 399 # turned off the phone and back on again. So if there is more 400 # than one 7e in the escaped data we should start after the 401 # second to last one 402 d=data.rfind(common.pppterminator,0,-1) 403 if d>=0: 404 self.log("Multiple PB packets in data - taking last one starting at "+`d+1`) 405 self.logdata("Original pb data", origdata, None) 406 data=data[d+1:] 407 408 # turn it back to normal 409 data=common.pppunescape(data) 410 411 # sometimes there is other crap at the begining 412 d=data.find(first) 413 if d>0: 414 self.log("Junk at begining of pb packet, data at "+`d`) 415 self.logdata("Original pb data", origdata, None) 416 self.logdata("Working on pb data", data, None) 417 data=data[d:] 418 # take off crc and terminator 419 crc=data[-3:-1] 420 data=data[:-3] 421 if common.crcs(data)!=crc: 422 self.logdata("Original pb data", origdata, None) 423 self.logdata("Working on pb data", data, None) 424 raise common.CommsDataCorruption("toshiba phonebook packet failed CRC check", self.desc) 425 426 # parse data 427 buffer=prototypes.buffer(data) 428 res=responseclass() 429 res.readfrombuffer(buffer, logtitle="toshiba phonebook response") 430 return res
431
432 - def get_detect_data(self, res):
433 try: 434 self.modemmoderequest() 435 res['manufacturer']=self.comm.sendatcommand('+GMI')[0] 436 res['model']=self.comm.sendatcommand('+GMM')[0] 437 res['firmware_version']=self.comm.sendatcommand('+GMR')[0] 438 res['esn']=self.comm.sendatcommand('+GSN')[0][2:] # strip off the 0x 439 except commport.CommTimeout: 440 pass
441 442 @classmethod
443 - def detectphone(_, coms, likely_ports, res, _module, _log):
444 if not likely_ports: 445 # cannot detect any likely ports 446 return None 447 for port in likely_ports: 448 if not res.has_key(port): 449 res[port]={ 'mode_modem': None, 'mode_brew': None, 450 'manufacturer': None, 'model': None, 451 'firmware_version': None, 'esn': None, 452 'firmwareresponse': None } 453 try: 454 if res[port]['model']: 455 # is a model has already 456 # been found, not much we can do now 457 continue 458 p=_module.Phone(_log, commport.CommConnection(_log, port, timeout=1)) 459 # because this is a modem port we ignore any existing results for likely ports 460 res[port]['mode_brew']=p._setmodebrew() 461 if res[port]['mode_brew']: 462 p.get_detect_data(res[port]) 463 p.comm.close() 464 except: 465 if __debug__: 466 raise
467
468 -class Profile(com_phone.Profile):
469 470 protocolclass=Phone.protocolclass 471 serialsname=Phone.serialsname 472 473 # use for auto-detection 474 phone_manufacturer='Audiovox Communications Corporation' 475 phone_model='VM4050' 476 477 # which usb ids correspond to us 478 usbids=((0x05C6, 0x3100, 1), # toshiba modem port direct cable connection 479 ) 480 # which device classes we are. 481 deviceclasses=("modem",) 482 483 # what types of syncing we support 484 _supportedsyncs=( 485 ('phonebook', 'read', None), # all phonebook reading 486 ('phonebook', 'write', 'OVERWRITE'), # phonebook overwrite only 487 ) 488
489 - def convertphonebooktophone(self, helper, data):
490 """Converts the data to what will be used by the phone 491 492 @param data: contains the dict returned by getfundamentals 493 as well as where the results go""" 494 results={} 495 slotsused={} 496 # generate a list of used slots 497 for pbentry in data['phonebook']: 498 entry=data['phonebook'][pbentry] 499 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 500 if(serial1 >= 0 and serial1 < self.protocolclass.NUMSLOTS): 501 slotsused[serial1]=1 502 503 lastunused=0 # One more than last unused slot 504 505 for pbentry in data['phonebook']: 506 if len(results)==self.protocolclass.NUMSLOTS: 507 break 508 e={} # entry out 509 entry=data['phonebook'][pbentry] # entry in 510 try: 511 # name 512 e['name']=helper.getfullname(entry.get('names', []),1,1,36)[0] 513 514 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 515 516 if(serial1 >= 0 and serial1 < self.protocolclass.NUMSLOTS): 517 e['slot']=serial1 518 else: # A new entry. Must find unused slot 519 while(slotsused.has_key(lastunused)): 520 lastunused+=1 521 if(lastunused >= self.protocolclass.NUMSLOTS): 522 helper.add_error_message("Name: %s. Unable to add, phonebook full" % e['name']) 523 raise helper.ConversionFailed() 524 e['slot']=lastunused 525 slotsused[lastunused]=1 526 527 # email addresses 528 emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.MAXEMAILS,self.protocolclass.MAXEMAILLEN) 529 e['emails']=helper.filllist(emails, self.protocolclass.MAXEMAILS, "") 530 531 # url 532 e['web_page']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "") 533 534 # phone numbers 535 numbers=helper.getnumbers(entry.get('numbers', []),0,self.protocolclass.MAXPHONENUMBERS) 536 e['numbertypes']=[] 537 e['numbers']=[] 538 typesused={} 539 for num in numbers: 540 type=num['type'] 541 if(typesused.has_key(type)): 542 continue 543 typesused[type]=1 544 for typenum,tnsearch in enumerate(self.protocolclass.numbertypetab): 545 if type==tnsearch: 546 number=self.phonize(num['number']) 547 if len(number)==0: 548 # no actual digits in the number 549 continue 550 if len(number)>self.protocolclass.MAXPHONENUMBERLEN: # get this number from somewhere sensible 551 # :: TODO:: number is too long and we have to either truncate it or ignore it? 552 number=number[:self.protocolclass.MAXPHONENUMBERLEN] 553 e['numbers'].append(number) 554 e['numbertypes'].append(typenum) 555 break 556 557 # ringtones 558 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None) 559 560 # flags 561 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False) 562 563 results[pbentry]=e 564 565 except helper.ConversionFailed: 566 continue 567 data['phonebook']=results 568 return data
569 570
571 - def phonize(self, str):
572 """Convert the phone number into something the phone understands 573 574 All digits, P, T, *, # are kept, everything else is removed. 575 In theory the phone can store a dash in the phonebook, but that 576 is not normal.""" 577 return re.sub("[^0-9PT#*]", "", str)[:self.protocolclass.MAXPHONENUMBERLEN]
578