0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2004 Roger Binns <rogerb@rogerbinns.com> 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_audiovoxcdm8900.py 3037 2006-04-03 00:30:28Z rogerb $ 0009 0010 """Communicate with the Audiovox CDM 8900 cell phone""" 0011 0012 # standard modules 0013 import sha 0014 import re 0015 0016 # our modules 0017 import common 0018 import com_phone 0019 import com_brew 0020 import prototypes 0021 import p_audiovoxcdm8900 0022 0023 class Phone(com_phone.Phone, com_brew.BrewProtocol): 0024 "Talk to Audiovox CDM 8900 cell phone" 0025 0026 desc="Audiovox CDM8900" 0027 protocolclass=p_audiovoxcdm8900 0028 serialsname='audiovoxcdm8900' 0029 pbterminator="~" # packet terminator 0030 0031 def __init__(self, logtarget, commport): 0032 "Calls all the constructors and sets initial modes" 0033 com_phone.Phone.__init__(self, logtarget, commport) 0034 com_brew.BrewProtocol.__init__(self) 0035 self.log("Attempting to contact phone") 0036 self.mode=self.MODENONE 0037 0038 def getfundamentals(self, results): 0039 """Gets information fundamental to interopating with the phone and UI. 0040 0041 Currently this is: 0042 0043 - 'uniqueserial' a unique serial number representing the phone 0044 - 'groups' the phonebook groups 0045 - 'wallpaper-index' map index numbers to names 0046 - 'ringtone-index' map index numbers to ringtone names 0047 0048 This method is called before we read the phonebook data or before we 0049 write phonebook data. 0050 """ 0051 # use a hash of ESN and other stuff (being paranoid) 0052 self.log("Retrieving fundamental phone information") 0053 self.setmode(self.MODEBREW) 0054 self.log("Phone serial number") 0055 results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest() 0056 0057 # now read groups 0058 self.log("Reading group information") 0059 groups={} 0060 for i in range(self.protocolclass._NUMGROUPS): 0061 req=self.protocolclass.readgroupentryrequest() 0062 req.number=i 0063 res=self.sendpbcommand(req, self.protocolclass.readgroupentryresponse) 0064 if res.number==self.protocolclass._ALLGROUP: 0065 continue # ignore the "All" group 0066 if len(res.name)==0: 0067 continue # must be non-blank name 0068 groups[i]={'name': res.name} 0069 0070 results['groups']=groups 0071 0072 self.log("Fundamentals retrieved") 0073 return results 0074 0075 def getcalendar(self, result): 0076 raise NotImplementedError() 0077 0078 def getwallpapers(self, result): 0079 raise NotImplementedError() 0080 0081 def getringtones(self, result): 0082 raise NotImplementedError() 0083 0084 def getphonebook(self, result): 0085 """Reads the phonebook data. The L{getfundamentals} information will 0086 already be in result.""" 0087 pbook={} 0088 req=self.protocolclass.readpbslotsrequest() 0089 res=self.sendpbcommand(req, self.protocolclass.readpbslotsresponse) 0090 slots=[x for x in range(len(res.present)) if ord(res.present[x])] 0091 numentries=len(slots) 0092 for i in range(numentries): 0093 req=self.protocolclass.readpbentryrequest() 0094 req.slotnumber=slots[i] 0095 res=self.sendpbcommand(req, self.protocolclass.readpbentryresponse) 0096 self.log("Read entry "+`i`+" - "+res.entry.name) 0097 self.progress(i, numentries, res.entry.name) 0098 entry=self.extractphonebookentry(slots[i], res.entry, result) 0099 pbook[i]=entry 0100 self.progress(i, numentries, res.entry.name) 0101 self.progress(numentries, numentries, "Phone book read completed") 0102 result['phonebook']=pbook 0103 0104 for i in range(0x1e): 0105 req=self.protocolclass.dunnorequest() 0106 req.which=i 0107 self.sendpbcommand(req, self.protocolclass.dunnoresponse) 0108 0109 return pbook 0110 0111 def extractphonebookentry(self, slotnumber, entry, result): 0112 """Return a phonebook entry in BitPim format. This is called from getphonebook.""" 0113 res={} 0114 # serials 0115 res['serials']=[ {'sourcetype': self.serialsname, 'slot': slotnumber, 0116 'sourceuniqueid': result['uniqueserial']} ] 0117 # numbers 0118 numbers=[] 0119 for t, v in ( ('cell', entry.mobile), ('home', entry.home), ('office', entry.office), 0120 ('pager', entry.pager), ('fax', entry.fax) ): 0121 if len(v)==0: 0122 continue 0123 numbers.append( {'number': v, 'type': t} ) 0124 if len(numbers): 0125 res['numbers']=numbers 0126 # name 0127 if len(entry.name): # yes, the audiovox can have a blank name! 0128 res['names']=[{'full': entry.name}] 0129 # emails (we treat wireless as email addr) 0130 emails=[] 0131 if len(entry.email): 0132 emails.append({'email': entry.email}) 0133 if len(entry.wireless): 0134 emails.append({'email': entry.wireless}) 0135 if len(emails): 0136 res['emails']=emails 0137 # memo 0138 if len(entry.memo): 0139 res['memos']=[{'memo': entry.memo}] 0140 # secret 0141 if entry.secret: 0142 res['flags']=[{'secret': True}] 0143 # group 0144 if entry.group in result['groups']: 0145 group=result['groups'][entry.group]['name'] 0146 if group!="Etc.": # this is the "bucket" group on the phone, which we treat as no group at all 0147 res['categories']=[{'category': group}] 0148 # media 0149 rt=[] 0150 if entry.ringtone!=0xffff: 0151 rt.append({'ringtone': 'avox '+`entry.ringtone`, 'use': 'call'}) 0152 if entry.msgringtone!=0xffff: 0153 rt.append({'ringtone': 'avox '+`entry.msgringtone`, 'use': 'message'}) 0154 if len(rt): 0155 res['ringtones']=rt 0156 if entry.wallpaper!=0xffff: 0157 res['wallpapers']=[{'wallpaper': 'avox '+`entry.wallpaper`, 'use': 'call'}] 0158 return res 0159 0160 def makephonebookentry(self, fields): 0161 e=self.protocolclass.pbentry() 0162 # some defaults 0163 e.secret=0 0164 e.previous=0xffff 0165 e.next=0xffff 0166 e.ringtone=0xffff 0167 e.msgringtone=0xffff 0168 e.wallpaper=0xffff 0169 for f in fields: 0170 setattr(e, f, fields[f]) 0171 if e.group==0: 0172 raise Exception("Data error: The group cannot be zero or the phone crashes") 0173 return e 0174 0175 def savephonebook(self, data): 0176 self.log("Saving group information") 0177 0178 for gid in range(1, self.protocolclass._NUMGROUPS): # do not save group 0 - All 0179 name=data['groups'].get(gid, {'name': ''})['name'] 0180 req=self.protocolclass.writegroupentryrequest() 0181 req.number=gid 0182 req.anothernumber=gid 0183 req.name=name 0184 req.nummembers=data['groups'].get(gid, {'members': 0})['members'] 0185 self.log("Group #%d %s - %d members" % (gid, `name`, req.nummembers)) 0186 self.sendpbcommand(req, self.protocolclass.writegroupentryresponse) 0187 0188 0189 self.log("New phonebook\n"+common.prettyprintdict(data['phonebook'])) 0190 0191 # in theory we should offline the phone and wait two seconds first ... 0192 0193 pb=data['phonebook'] 0194 keys=pb.keys() 0195 keys.sort() 0196 keys=keys[:self.protocolclass._NUMSLOTS] 0197 # work out the bitmap. 0198 slots=[] 0199 for i in range(self.protocolclass._NUMSLOTS): 0200 if i not in keys: 0201 slots.append(0) 0202 continue 0203 bmp=0 0204 e=pb[i] 0205 if len(e['mobile']): bmp|=1 0206 if len(e['home']): bmp|=2 0207 if len(e['office']): bmp|=4 0208 if len(e['pager']): bmp|=8 0209 if len(e['fax']): bmp|=16 0210 if len(e['email']): bmp|=32 0211 if len(e['wireless']): bmp|=64 0212 slots.append(bmp) 0213 slots="".join([chr(x) for x in slots]) 0214 req=self.protocolclass.writepbslotsrequest() 0215 req.present=slots 0216 self.sendpbcommand(req, self.protocolclass.writepbslotsresponse) 0217 # now write out each slot 0218 for i in range(len(keys)): 0219 slot=keys[i] 0220 req=self.protocolclass.writepbentryrequest() 0221 req.slotnumber=slot 0222 req.entry=self.makephonebookentry(pb[slot]) 0223 self.log('Writing entry '+`slot`+" - "+req.entry.name) 0224 self.progress(i, len(keys), "Writing "+req.entry.name) 0225 self.sendpbcommand(req, self.protocolclass.writepbentryresponse) 0226 self.progress(len(keys)+1, len(keys)+1, "Phone book write completed - phone will be rebooted") 0227 data['rebootphone']=True 0228 return data 0229 0230 0231 def sendpbcommand(self, request, responseclass): 0232 self.setmode(self.MODEBREW) 0233 buffer=prototypes.buffer() 0234 request.writetobuffer(buffer, logtitle="audiovox cdm8900 phonebook request") 0235 data=buffer.getvalue() 0236 data=common.pppescape(data+common.crcs(data))+common.pppterminator 0237 first=data[0] 0238 try: 0239 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 0240 except com_phone.modeignoreerrortypes: 0241 self.mode=self.MODENONE 0242 self.raisecommsdnaexception("manipulating the phonebook") 0243 self.comm.success=True 0244 0245 origdata=data 0246 # sometimes there is junk at the begining, eg if the user 0247 # turned off the phone and back on again. So if there is more 0248 # than one 7e in the escaped data we should start after the 0249 # second to last one 0250 d=data.rfind(common.pppterminator,0,-1) 0251 if d>=0: 0252 self.log("Multiple PB packets in data - taking last one starting at "+`d+1`) 0253 self.logdata("Original pb data", origdata, None) 0254 data=data[d+1:] 0255 0256 # turn it back to normal 0257 data=common.pppunescape(data) 0258 0259 # sometimes there is other crap at the begining 0260 d=data.find(first) 0261 if d>0: 0262 self.log("Junk at begining of pb packet, data at "+`d`) 0263 self.logdata("Original pb data", origdata, None) 0264 self.logdata("Working on pb data", data, None) 0265 data=data[d:] 0266 # take off crc and terminator 0267 crc=data[-3:-1] 0268 data=data[:-3] 0269 if common.crcs(data)!=crc: 0270 self.logdata("Original pb data", origdata, None) 0271 self.logdata("Working on pb data", data, None) 0272 raise common.CommsDataCorruption("Audiovox phonebook packet failed CRC check", self.desc) 0273 0274 # parse data 0275 buffer=prototypes.buffer(data) 0276 res=responseclass() 0277 res.readfrombuffer(buffer, logtitle="Audiovox phonebook response") 0278 return res 0279 0280 0281 0282 class Profile(com_phone.Profile): 0283 0284 protocolclass=Phone.protocolclass 0285 serialsname=Phone.serialsname 0286 0287 WALLPAPER_WIDTH=128 0288 WALLPAPER_HEIGHT=145 0289 WALLPAPER_CONVERT_FORMAT="jpg" 0290 0291 MAX_WALLPAPER_BASENAME_LENGTH=16 0292 WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ." 0293 0294 MAX_RINGTONE_BASENAME_LENGTH=16 0295 RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ." 0296 0297 # which usb ids correspond to us 0298 usbids=( (0x106c, 0x2101, 1), # VID=Curitel, PID=Audiovox CDM 8900, internal modem interface 0299 ) 0300 # which device classes we are. 0301 deviceclasses=("modem",) 0302 0303 # what types of syncing we support 0304 _supportedsyncs=( 0305 ('phonebook', 'read', None), # all phonebook reading 0306 ('phonebook', 'write', 'OVERWRITE'), # phonebook overwrite only 0307 ) 0308 0309 def _getgroup(self, name, groups): 0310 for key in groups: 0311 if groups[key]['name']==name: 0312 return key,groups[key] 0313 return None,None 0314 0315 def normalisegroups(self, helper, data): 0316 "Assigns groups based on category data" 0317 0318 keys=data['groups'].keys() 0319 keys.sort() 0320 pad=[data['groups'][k]['name'] for k in keys if k] # ignore key 0 - All 0321 0322 groups=helper.getmostpopularcategories(self.protocolclass._NUMGROUPS, data['phonebook'], ["All", "Business", "Personal", "Etc."], 0323 self.protocolclass._MAXGROUPLEN, pad) 0324 0325 # alpha sort 0326 groups.sort() 0327 0328 # newgroups 0329 newgroups={} 0330 0331 # put in No group 0332 newgroups[0]={'name': 'All', 'members': 0} 0333 0334 # populate 0335 for name in groups: 0336 # existing entries remain unchanged 0337 if name=="All": continue 0338 key,value=self._getgroup(name, data['groups']) 0339 if key is not None and key!=0: 0340 newgroups[key]=value 0341 newgroups[key]['members']=0 0342 0343 # new entries get whatever numbers are free 0344 for name in groups: 0345 key,value=self._getgroup(name, newgroups) 0346 if key is None: 0347 for key in range(1,10000): 0348 if key not in newgroups: 0349 newgroups[key]={'name': name, 'members': 0} 0350 break 0351 # yay, done 0352 data['groups']=newgroups 0353 0354 def convertphonebooktophone(self, helper, data): 0355 """Converts the data to what will be used by the phone 0356 0357 @param data: contains the dict returned by getfundamentals 0358 as well as where the results go""" 0359 self.normalisegroups(helper, data) 0360 results={} 0361 0362 # find which entries are already known to this phone 0363 pb=data['phonebook'] 0364 # decorate list with (slot, pbkey) tuples 0365 slots=[ (helper.getserial(pb[pbentry].get("serials", []), self.serialsname, data['uniqueserial'], "slot", None), pbentry) 0366 for pbentry in pb] 0367 slots.sort() # numeric order 0368 # make two lists - one contains known slots, one doesn't 0369 newones=[(pbentry,slot) for slot,pbentry in slots if slot is None] 0370 existing=[(pbentry,slot) for slot,pbentry in slots if slot is not None] 0371 0372 for pbentry,slot in newones+existing: 0373 if len(results)==self.protocolclass._NUMSLOTS: 0374 break 0375 try: 0376 e={} # entry out 0377 entry=data['phonebook'][pbentry] 0378 e['mobile']=self.phonize(helper.getnumber(entry.get('numbers', []), 'cell')) 0379 e['home']=self.phonize(helper.getnumber(entry.get('numbers', []), 'home')) 0380 e['office']=self.phonize(helper.getnumber(entry.get('numbers', []), 'office')) 0381 e['pager']=self.phonize(helper.getnumber(entry.get('numbers', []), 'pager')) 0382 e['fax']=self.phonize(helper.getnumber(entry.get('numbers', []), 'fax')) 0383 emails=helper.getemails(entry.get('emails', []), 0, 2, self.protocolclass._MAXEMAILLEN) 0384 e['email']="" 0385 e['wireless']="" 0386 if len(emails)>=1: 0387 e['email']=emails[0] 0388 if len(emails)>=2: 0389 e['wireless']=emails[1] 0390 0391 # must be at least one number or email 0392 if max([len(e[field]) for field in e])==0: 0393 # ::TODO:: log that we are ignoring this entry 0394 continue 0395 0396 e['name']=helper.getfullname(entry.get('names', [ {'full': ''}]), 1, 1, self.protocolclass._MAXNAMELEN)[0] 0397 e['memo']=helper.getmemos(entry.get('memos', [{'memo': ''}]), 1,1, self.protocolclass._MAXMEMOLEN)[0] 0398 0399 rt=helper.getringtone(entry.get('ringtones', []), 'call', "") 0400 if rt.startswith("avox "): 0401 e['ringtone']=int(rt[5:]) 0402 0403 rt=helper.getringtone(entry.get('ringtones', []), 'message', "") 0404 if rt.startswith("avox "): 0405 e['msgringtone']=int(rt[5:]) 0406 0407 wp=helper.getwallpaper(entry.get('wallpapers', []), 'call', "") 0408 if wp.startswith("avox "): 0409 e['wallpaper']=int(wp[5:]) 0410 0411 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False) 0412 0413 # deal with group 0414 group=helper.getcategory(entry.get('categories', [{'category': 'Etc.'}]),1,1,self.protocolclass._MAXGROUPLEN)[0] 0415 gid,_=self._getgroup(group, data['groups']) 0416 if gid is None or gid==0: 0417 gid,_=self._getgroup("Etc.", data['groups']) 0418 assert gid!=0 0419 e['group']=gid 0420 data['groups'][gid]['members']+=1 0421 # find the right slot 0422 if slot is None or slot<0 or slot>=self.protocolclass._NUMSLOTS or slot in results: 0423 for i in range(100000): 0424 if i not in results: 0425 slot=i 0426 break 0427 results[slot]=e 0428 except helper.ConversionFailed: 0429 continue 0430 data['phonebook']=results 0431 return data 0432 0433 0434 def phonize(self, str): 0435 """Convert the phone number into something the phone understands 0436 0437 All digits, P, T, *, # are kept, everything else is removed. 0438 In theory the phone can store a dash in the phonebook, but that 0439 is not normal.""" 0440 return re.sub("[^0-9PT#*]", "", str)[:self.protocolclass._MAXPHONENUMBERLEN] 0441
Generated by PyXR 0.9.4