0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2005 Simon Capper <scapper@sbcglobal.net> 0004 ### 0005 ### This program is free software; you can redistribute it and/or modify 0006 ### it under the terms of the BitPim license as detailed in the LICENSE file. 0007 ### 0008 ### $Id: com_toshibavm4050.py 4516 2007-12-21 22:00:57Z djpham $ 0009 0010 """Communicate with the toshiba CDM 8900 cell phone""" 0011 0012 # standard modules 0013 import sha 0014 import re 0015 import time 0016 0017 # our modules 0018 import common 0019 import com_phone 0020 import com_brew 0021 import prototypes 0022 import commport 0023 import p_brew 0024 import p_toshibavm4050 0025 import helpids 0026 0027 class Phone(com_phone.Phone, com_brew.BrewProtocol): 0028 "Talk to Toshiba VM4050 cell phone" 0029 0030 desc="Toshiba VM4050" 0031 helpid=helpids.ID_PHONE_TOSHIBAVM4050 0032 protocolclass=p_toshibavm4050 0033 serialsname='toshibavm4050' 0034 0035 builtinimages=() 0036 0037 builtinringtones=( 'Common', 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5', 0038 'Ring 6', 'Ring 7', 'Ring 8', 'Ring 9', 0039 'Alarm 1', 'Alarm 2', 'Alarm 3', 'Alarm 4', 'Alarm 5', 0040 'Alarm 6', 'Alarm 7', 'Alarm 8', 'Alarm 9', 0041 'Stars & Stripes', 'When the Saints', 'Bach', 0042 'William Tell', 'Nachtmusik', 'Str Serenade', 'Toccata', 0043 'Meistersinger', 'Blue Danube') 0044 0045 def __init__(self, logtarget, commport): 0046 "Calls all the constructors and sets initial modes" 0047 com_phone.Phone.__init__(self, logtarget, commport) 0048 com_brew.BrewProtocol.__init__(self) 0049 self.log("Attempting to contact phone") 0050 self.mode=self.MODENONE 0051 0052 def getfundamentals(self, results): 0053 """Gets information fundamental to interopating with the phone and UI. 0054 0055 Currently this is: 0056 0057 - 'uniqueserial' a unique serial number representing the phone 0058 - 'groups' the phonebook groups 0059 - 'wallpaper-index' map index numbers to names 0060 - 'ringtone-index' map index numbers to ringtone names 0061 0062 This method is called before we read the phonebook data or before we 0063 write phonebook data. 0064 """ 0065 self.log("Getting fundamentals") 0066 # use a hash of ESN and other stuff (being paranoid) 0067 self.log("Retrieving fundamental phone information") 0068 self.setmode(self.MODEBREW) 0069 self.log("Phone serial number") 0070 results['uniqueserial']=sha.new(self.get_esn()).hexdigest() 0071 # This phone does not support groups 0072 results['groups']={} 0073 #self.getwallpaperindices(results) 0074 self.getringtoneindices(results) 0075 self.log("Fundamentals retrieved") 0076 return results 0077 0078 def getmediaindex(self, builtins, results, key): 0079 """Gets the media (wallpaper/ringtone) index 0080 0081 @param builtins: the builtin list on the phone 0082 @param results: places results in this dict 0083 @param maps: the list of index files and locations 0084 @param key: key to place results in 0085 """ 0086 0087 self.log("Reading "+key) 0088 media={} 0089 0090 # builtins 0091 c=1 0092 for name in builtins: 0093 if name: 0094 media[c]={'name': name, 'origin': 'builtin' } 0095 c+=1 0096 0097 results[key]=media 0098 return media 0099 0100 def getwallpaperindices(self, results): 0101 return self.getmediaindex(self.builtinimages, results, 'wallpaper-index') 0102 0103 def getringtoneindices(self, results): 0104 return self.getmediaindex(self.builtinringtones, results, 'ringtone-index') 0105 0106 def listsubdirs(self, dir='', recurse=0): 0107 self.log("Getting file system in dir '"+dir+"'") 0108 res=com_brew.BrewProtocol.listsubdirs(self, dir, recurse) 0109 # toshiba is bad, they don't list these directories when you query the phone 0110 # so we add them in ourselves 0111 if not len(dir): 0112 res['C']={ 'name': 'C', 'type': 'directory' } 0113 res['D']={ 'name': 'D', 'type': 'directory' } 0114 res['G']={ 'name': 'G', 'type': 'directory' } 0115 return res 0116 0117 def enable_data_transfer(self): 0118 req=self.protocolclass.setphoneattribrequest() 0119 res=self.sendpbcommand(req, self.protocolclass.setphoneattribresponse) 0120 req=self.protocolclass.tosh_enableswapdatarequest() 0121 res=self.sendpbcommand(req, self.protocolclass.tosh_enableswapdataresponse) 0122 0123 def disable_data_transfer(self): 0124 req=self.protocolclass.tosh_disableswapdatarequest() 0125 res=self.sendpbcommand(req, self.protocolclass.tosh_disableswapdataresponse) 0126 0127 def readdatarecord(self): 0128 return self.getfilecontents("D/APL/SWAP/DIAG_APSWP_MEMORY") 0129 0130 def writedatarecord(self, record): 0131 return self.writefile("D/APL/SWAP/DIAG_APSWP_MEMORY", record) 0132 0133 def get_esn(self, data=None): 0134 # return the ESN of this phone 0135 return self.get_brew_esn() 0136 0137 def getcalendar(self, result): 0138 raise NotImplementedError() 0139 0140 def getwallpapers(self, result): 0141 raise NotImplementedError() 0142 0143 def getringtones(self, result): 0144 raise NotImplementedError() 0145 0146 def getphonebook(self, result): 0147 """Reads the phonebook data. The L{getfundamentals} information will 0148 already be in result.""" 0149 pbook={} 0150 self.enable_data_transfer() 0151 # no way to read the number of entries, just have to read all 0152 count=0 0153 # try: 0154 for i in range(self.protocolclass.NUMSLOTS): 0155 entry=self.protocolclass.pbentry() 0156 self.progress(i, self.protocolclass.NUMSLOTS, "") 0157 req=self.protocolclass.tosh_getpbentryrequest() 0158 req.entry_index=i 0159 res=self.sendpbcommand(req, self.protocolclass.tosh_getpbentryresponse) 0160 if not res.swap_ok: 0161 raw=self.readdatarecord() 0162 if __debug__: 0163 open("c:/projects/temp/record_in"+`i`, "wb").write(raw) 0164 buf=prototypes.buffer(raw) 0165 entry.readfrombuffer(buf, logtitle="phonebook data record") 0166 else: 0167 continue 0168 self.log("Read entry "+`i`+" - "+entry.name) 0169 pb_entry=self.extractphonebookentry(entry, result) 0170 pbook[i]=pb_entry 0171 #except Exception, e: 0172 # must disable this to prevent phone problems 0173 # self.disable_data_transfer() 0174 # raise Exception, e 0175 self.disable_data_transfer() 0176 self.progress(self.protocolclass.NUMSLOTS, self.protocolclass.NUMSLOTS, "Phone book read completed") 0177 result['phonebook']=pbook 0178 return result 0179 0180 _type_map={ 0181 0: 'phone', 0182 1: 'home', 0183 2: 'office', 0184 3: 'cell', 0185 4: 'pager', 0186 5: 'fax', 0187 } 0188 0189 _ringer_pattern1_map={ 0190 0x0100: 'Ring 1', 0191 0x0101: 'Ring 2', 0192 0x0102: 'Ring 3', 0193 0x0103: 'Ring 4', 0194 0x0104: 'Ring 5', 0195 0x0105: 'Ring 6', 0196 0x0106: 'Ring 7', 0197 0x0107: 'Ring 8', 0198 0x0108: 'Ring 9', 0199 } 0200 0201 _ringer_pattern2_map={ 0202 0x0009: 'Alarm 1', 0203 0x000A: 'Alarm 2', 0204 0x000B: 'Alarm 3', 0205 0x000C: 'Alarm 4', 0206 0x000D: 'Alarm 5', 0207 0x000E: 'Alarm 6', 0208 0x000F: 'Alarm 7', 0209 0x0010: 'Alarm 8', 0210 0x0011: 'Alarm 9', 0211 } 0212 0213 _ringer_melody_map={ 0214 0x0000: 'Stars & Stripes', 0215 0x0001: 'When the Saints', 0216 0x0002: 'Bach', 0217 0x0003: 'William Tell', 0218 0x0004: 'Nachtmusik', 0219 0x0005: 'Str Serenade', 0220 0x0006: 'Toccata', 0221 0x0007: 'Meistersinger', 0222 0x0008: 'Blue Danube', 0223 } 0224 0225 def _get_ringtone(self, ringer_group, ringer_index): 0226 # no idea what values downloaded ringtones use 0227 # exception handler will take care of any wierd 0228 # value we do not understand 0229 try: 0230 if ringer_group==1: 0231 return self._ringer_pattern1_map[ringer_index] 0232 if ringer_group==2: 0233 return self._ringer_pattern2_map[ringer_index] 0234 if ringer_group==3: 0235 return self._ringer_melody_map[ringer_index] 0236 except: 0237 pass 0238 return 'Common' 0239 0240 def __find_index(self, map, ringer): 0241 for k, v in map.iteritems(): 0242 if v==ringer: 0243 return k 0244 return -1 0245 0246 def _getringtone_group_index(self, ringtone): 0247 ringer_index = self.__find_index(self._ringer_pattern1_map,ringtone) 0248 if ringer_index != -1: 0249 return 1, ringer_index 0250 ringer_index = self.__find_index(self._ringer_pattern2_map,ringtone) 0251 if ringer_index != -1: 0252 return 2, ringer_index 0253 ringer_index = self.__find_index(self._ringer_melody_map,ringtone) 0254 if ringer_index != -1: 0255 return 3, ringer_index 0256 return 5, 0 0257 0258 def extractphonebookentry(self, entry, result): 0259 """Return a phonebook entry in BitPim format. This is called from getphonebook.""" 0260 res={} 0261 # serials 0262 res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.slot, 0263 'sourceuniqueid': result['uniqueserial']} ] 0264 # numbers 0265 set=False 0266 secret=False 0267 ringtone='Common' 0268 numbers=[] 0269 for i in range(self.protocolclass.MAXPHONENUMBERS): 0270 if entry.numbers[i].valid and len(entry.numbers[i].number): 0271 type=self._type_map[entry.numbers[i].type] 0272 numbers.append( {'number': entry.numbers[i].number, 'type': type} ) 0273 # secret and ringtone are per number on this phone, bitpim only supports one 0274 # per phonebook entry, so we set the phone entry to secret if any number is secret 0275 # and we pick the first ringtone that is not the default and apply to the whole entry 0276 if not secret and entry.numbers[i].secret: 0277 secret=True 0278 if ringtone=='Common' and entry.numbers[i].ringer_group!=5: 0279 ringtone=self._get_ringtone(entry.numbers[i].ringer_group, entry.numbers[i].ringer_index) 0280 if len(numbers): 0281 res['numbers']=numbers 0282 if secret: 0283 res['flags']=[{'secret': True}] 0284 res['ringtones']=[{'ringtone': ringtone, 'use': 'call'}] 0285 # name 0286 if len(entry.name): # yes, the toshiba can have a blank name! 0287 res['names']=[{'full': entry.name}] 0288 # emails (we treat wireless as email addr) 0289 emails=[] 0290 for i in range(self.protocolclass.MAXEMAILS): 0291 if entry.emails[i].valid and len(entry.emails[i].email): 0292 emails.append( {'email': entry.emails[i].email} ) 0293 if len(emails): 0294 res['emails']=emails 0295 # urls 0296 if len(entry.web_page): 0297 res['urls']=[ {'url': entry.web_page} ] 0298 return res 0299 0300 def makephonebookentry(self, fields): 0301 e=self.protocolclass.pbentry() 0302 # some defaults 0303 secret=False 0304 if fields['secret']!=None: 0305 secret=fields['secret'] 0306 ringtone='Common' 0307 if fields['ringtone']!=None: 0308 ringtone=fields['ringtone'] 0309 ringer_group, ringer_index=self._getringtone_group_index(ringtone) 0310 if fields['name']: 0311 e.name=fields['name'] 0312 else: 0313 e.name='' 0314 for i in range(len(fields['numbers'])): 0315 n=self.protocolclass.pbnumber() 0316 n.valid=1 0317 n.number=fields['numbers'][i] 0318 n.type=fields['numbertypes'][i] 0319 n.secret=secret 0320 n.ringer_group=ringer_group 0321 n.ringer_index=ringer_index 0322 e.numbers.append(n) 0323 for i in range(len(fields['emails'])): 0324 n=self.protocolclass.pbemail() 0325 n.valid=1 0326 n.email=fields['emails'][i] 0327 e.emails.append(n) 0328 if fields['web_page']: 0329 e.web_page=fields['web_page'] 0330 e.slot=fields['slot'] 0331 return e 0332 0333 def savephonebook(self, data): 0334 # brute force, it is faster, otherwise we would have to 0335 # examine each entry to see if it was there and then decide what 0336 # to do with it, deleting and re-writing is faster and what the user 0337 # seleted with "OVERWRITE" 0338 self.log("New phonebook\n"+common.prettyprintdict(data['phonebook'])) 0339 pb=data['phonebook'] 0340 keys=pb.keys() 0341 keys.sort() 0342 keys=keys[:self.protocolclass.NUMSLOTS] 0343 self.enable_data_transfer() 0344 try: 0345 # delete the old phonebook 0346 self.rmfile("D/APL/SWAP/DIAG_APSWP_MEMORY") 0347 for i in range(self.protocolclass.NUMSLOTS): 0348 self.deletepbentry(i) 0349 # create the new phonebook 0350 for i in range(len(keys)): 0351 slot=keys[i] 0352 entry=self.makephonebookentry(pb[slot]) 0353 self.progress(i, len(keys), "Writing "+entry.name) 0354 self.log('Writing entry '+`slot`+" - "+entry.name) 0355 self.sendpbentrytophone(entry) 0356 except Exception, e: 0357 # must disable this to prevent phone problems 0358 self.disable_data_transfer() 0359 raise Exception, e 0360 self.disable_data_transfer() 0361 self.progress(len(keys)+1, len(keys)+1, "Phone book write completed") 0362 return data 0363 0364 def sendpbentrytophone(self, entry): 0365 # write out the new one 0366 buf=prototypes.buffer() 0367 entry.writetobuffer(buf, logtitle="Sending pbentrytophone") 0368 data=buf.getvalue() 0369 self.writedatarecord(data) 0370 req=self.protocolclass.tosh_setpbentryrequest() 0371 req.entry_index=entry.slot 0372 if __debug__: 0373 open("c:/projects/temp/record_out"+`entry.slot`, "wb").write(data) 0374 res=self.sendpbcommand(req, self.protocolclass.tosh_setpbentryresponse) 0375 if res.swap_ok: 0376 self.log("Error writing phonebook entry") 0377 0378 def deletepbentry(self, slot): 0379 req=self.protocolclass.tosh_modifypbentryrequest() 0380 req.entry_index=slot 0381 res=self.sendpbcommand(req, self.protocolclass.tosh_modifypbentryresponse) 0382 0383 def sendpbcommand(self, request, responseclass): 0384 self.setmode(self.MODEBREW) 0385 buffer=prototypes.buffer() 0386 request.writetobuffer(buffer, logtitle="toshiba vm4050 phonebook request") 0387 data=buffer.getvalue() 0388 data=common.pppescape(data+common.crcs(data))+common.pppterminator 0389 first=data[0] 0390 try: 0391 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 0392 except com_phone.modeignoreerrortypes: 0393 self.mode=self.MODENONE 0394 self.raisecommsdnaexception("manipulating the phonebook") 0395 self.comm.success=True 0396 0397 origdata=data 0398 # sometimes there is junk at the begining, eg if the user 0399 # turned off the phone and back on again. So if there is more 0400 # than one 7e in the escaped data we should start after the 0401 # second to last one 0402 d=data.rfind(common.pppterminator,0,-1) 0403 if d>=0: 0404 self.log("Multiple PB packets in data - taking last one starting at "+`d+1`) 0405 self.logdata("Original pb data", origdata, None) 0406 data=data[d+1:] 0407 0408 # turn it back to normal 0409 data=common.pppunescape(data) 0410 0411 # sometimes there is other crap at the begining 0412 d=data.find(first) 0413 if d>0: 0414 self.log("Junk at begining of pb packet, data at "+`d`) 0415 self.logdata("Original pb data", origdata, None) 0416 self.logdata("Working on pb data", data, None) 0417 data=data[d:] 0418 # take off crc and terminator 0419 crc=data[-3:-1] 0420 data=data[:-3] 0421 if common.crcs(data)!=crc: 0422 self.logdata("Original pb data", origdata, None) 0423 self.logdata("Working on pb data", data, None) 0424 raise common.CommsDataCorruption("toshiba phonebook packet failed CRC check", self.desc) 0425 0426 # parse data 0427 buffer=prototypes.buffer(data) 0428 res=responseclass() 0429 res.readfrombuffer(buffer, logtitle="toshiba phonebook response") 0430 return res 0431 0432 def get_detect_data(self, res): 0433 try: 0434 self.modemmoderequest() 0435 res['manufacturer']=self.comm.sendatcommand('+GMI')[0] 0436 res['model']=self.comm.sendatcommand('+GMM')[0] 0437 res['firmware_version']=self.comm.sendatcommand('+GMR')[0] 0438 res['esn']=self.comm.sendatcommand('+GSN')[0][2:] # strip off the 0x 0439 except commport.CommTimeout: 0440 pass 0441 0442 @classmethod 0443 def detectphone(_, coms, likely_ports, res, _module, _log): 0444 if not likely_ports: 0445 # cannot detect any likely ports 0446 return None 0447 for port in likely_ports: 0448 if not res.has_key(port): 0449 res[port]={ 'mode_modem': None, 'mode_brew': None, 0450 'manufacturer': None, 'model': None, 0451 'firmware_version': None, 'esn': None, 0452 'firmwareresponse': None } 0453 try: 0454 if res[port]['model']: 0455 # is a model has already 0456 # been found, not much we can do now 0457 continue 0458 p=_module.Phone(_log, commport.CommConnection(_log, port, timeout=1)) 0459 # because this is a modem port we ignore any existing results for likely ports 0460 res[port]['mode_brew']=p._setmodebrew() 0461 if res[port]['mode_brew']: 0462 p.get_detect_data(res[port]) 0463 p.comm.close() 0464 except: 0465 if __debug__: 0466 raise 0467 0468 class Profile(com_phone.Profile): 0469 0470 protocolclass=Phone.protocolclass 0471 serialsname=Phone.serialsname 0472 0473 # use for auto-detection 0474 phone_manufacturer='Audiovox Communications Corporation' 0475 phone_model='VM4050' 0476 0477 # which usb ids correspond to us 0478 usbids=((0x05C6, 0x3100, 1), # toshiba modem port direct cable connection 0479 ) 0480 # which device classes we are. 0481 deviceclasses=("modem",) 0482 0483 # what types of syncing we support 0484 _supportedsyncs=( 0485 ('phonebook', 'read', None), # all phonebook reading 0486 ('phonebook', 'write', 'OVERWRITE'), # phonebook overwrite only 0487 ) 0488 0489 def convertphonebooktophone(self, helper, data): 0490 """Converts the data to what will be used by the phone 0491 0492 @param data: contains the dict returned by getfundamentals 0493 as well as where the results go""" 0494 results={} 0495 slotsused={} 0496 # generate a list of used slots 0497 for pbentry in data['phonebook']: 0498 entry=data['phonebook'][pbentry] 0499 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 0500 if(serial1 >= 0 and serial1 < self.protocolclass.NUMSLOTS): 0501 slotsused[serial1]=1 0502 0503 lastunused=0 # One more than last unused slot 0504 0505 for pbentry in data['phonebook']: 0506 if len(results)==self.protocolclass.NUMSLOTS: 0507 break 0508 e={} # entry out 0509 entry=data['phonebook'][pbentry] # entry in 0510 try: 0511 # name 0512 e['name']=helper.getfullname(entry.get('names', []),1,1,36)[0] 0513 0514 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 0515 0516 if(serial1 >= 0 and serial1 < self.protocolclass.NUMSLOTS): 0517 e['slot']=serial1 0518 else: # A new entry. Must find unused slot 0519 while(slotsused.has_key(lastunused)): 0520 lastunused+=1 0521 if(lastunused >= self.protocolclass.NUMSLOTS): 0522 helper.add_error_message("Name: %s. Unable to add, phonebook full" % e['name']) 0523 raise helper.ConversionFailed() 0524 e['slot']=lastunused 0525 slotsused[lastunused]=1 0526 0527 # email addresses 0528 emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.MAXEMAILS,self.protocolclass.MAXEMAILLEN) 0529 e['emails']=helper.filllist(emails, self.protocolclass.MAXEMAILS, "") 0530 0531 # url 0532 e['web_page']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "") 0533 0534 # phone numbers 0535 numbers=helper.getnumbers(entry.get('numbers', []),0,self.protocolclass.MAXPHONENUMBERS) 0536 e['numbertypes']=[] 0537 e['numbers']=[] 0538 typesused={} 0539 for num in numbers: 0540 type=num['type'] 0541 if(typesused.has_key(type)): 0542 continue 0543 typesused[type]=1 0544 for typenum,tnsearch in enumerate(self.protocolclass.numbertypetab): 0545 if type==tnsearch: 0546 number=self.phonize(num['number']) 0547 if len(number)==0: 0548 # no actual digits in the number 0549 continue 0550 if len(number)>self.protocolclass.MAXPHONENUMBERLEN: # get this number from somewhere sensible 0551 # :: TODO:: number is too long and we have to either truncate it or ignore it? 0552 number=number[:self.protocolclass.MAXPHONENUMBERLEN] 0553 e['numbers'].append(number) 0554 e['numbertypes'].append(typenum) 0555 break 0556 0557 # ringtones 0558 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None) 0559 0560 # flags 0561 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False) 0562 0563 results[pbentry]=e 0564 0565 except helper.ConversionFailed: 0566 continue 0567 data['phonebook']=results 0568 return data 0569 0570 0571 def phonize(self, str): 0572 """Convert the phone number into something the phone understands 0573 0574 All digits, P, T, *, # are kept, everything else is removed. 0575 In theory the phone can store a dash in the phonebook, but that 0576 is not normal.""" 0577 return re.sub("[^0-9PT#*]", "", str)[:self.protocolclass.MAXPHONENUMBERLEN] 0578
Generated by PyXR 0.9.4