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

Source Code for Module phones.com_audiovoxcdm8900

  1  ### BITPIM 
  2  ### 
  3  ### Copyright (C) 2004 Roger Binns <rogerb@rogerbinns.com> 
  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_audiovoxcdm8900.py 3037 2006-04-03 00:30:28Z rogerb $ 
  9   
 10  """Communicate with the Audiovox CDM 8900 cell phone""" 
 11   
 12  # standard modules 
 13  import sha 
 14  import re 
 15   
 16  # our modules 
 17  import common 
 18  import com_phone 
 19  import com_brew 
 20  import prototypes 
 21  import p_audiovoxcdm8900 
 22   
23 -class Phone(com_phone.Phone, com_brew.BrewProtocol):
24 "Talk to Audiovox CDM 8900 cell phone" 25 26 desc="Audiovox CDM8900" 27 protocolclass=p_audiovoxcdm8900 28 serialsname='audiovoxcdm8900' 29 pbterminator="~" # packet terminator 30
31 - def __init__(self, logtarget, commport):
32 "Calls all the constructors and sets initial modes" 33 com_phone.Phone.__init__(self, logtarget, commport) 34 com_brew.BrewProtocol.__init__(self) 35 self.log("Attempting to contact phone") 36 self.mode=self.MODENONE
37
38 - def getfundamentals(self, results):
39 """Gets information fundamental to interopating with the phone and UI. 40 41 Currently this is: 42 43 - 'uniqueserial' a unique serial number representing the phone 44 - 'groups' the phonebook groups 45 - 'wallpaper-index' map index numbers to names 46 - 'ringtone-index' map index numbers to ringtone names 47 48 This method is called before we read the phonebook data or before we 49 write phonebook data. 50 """ 51 # use a hash of ESN and other stuff (being paranoid) 52 self.log("Retrieving fundamental phone information") 53 self.setmode(self.MODEBREW) 54 self.log("Phone serial number") 55 results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest() 56 57 # now read groups 58 self.log("Reading group information") 59 groups={} 60 for i in range(self.protocolclass._NUMGROUPS): 61 req=self.protocolclass.readgroupentryrequest() 62 req.number=i 63 res=self.sendpbcommand(req, self.protocolclass.readgroupentryresponse) 64 if res.number==self.protocolclass._ALLGROUP: 65 continue # ignore the "All" group 66 if len(res.name)==0: 67 continue # must be non-blank name 68 groups[i]={'name': res.name} 69 70 results['groups']=groups 71 72 self.log("Fundamentals retrieved") 73 return results
74
75 - def getcalendar(self, result):
76 raise NotImplementedError()
77
78 - def getwallpapers(self, result):
79 raise NotImplementedError()
80
81 - def getringtones(self, result):
82 raise NotImplementedError()
83
84 - def getphonebook(self, result):
85 """Reads the phonebook data. The L{getfundamentals} information will 86 already be in result.""" 87 pbook={} 88 req=self.protocolclass.readpbslotsrequest() 89 res=self.sendpbcommand(req, self.protocolclass.readpbslotsresponse) 90 slots=[x for x in range(len(res.present)) if ord(res.present[x])] 91 numentries=len(slots) 92 for i in range(numentries): 93 req=self.protocolclass.readpbentryrequest() 94 req.slotnumber=slots[i] 95 res=self.sendpbcommand(req, self.protocolclass.readpbentryresponse) 96 self.log("Read entry "+`i`+" - "+res.entry.name) 97 self.progress(i, numentries, res.entry.name) 98 entry=self.extractphonebookentry(slots[i], res.entry, result) 99 pbook[i]=entry 100 self.progress(i, numentries, res.entry.name) 101 self.progress(numentries, numentries, "Phone book read completed") 102 result['phonebook']=pbook 103 104 for i in range(0x1e): 105 req=self.protocolclass.dunnorequest() 106 req.which=i 107 self.sendpbcommand(req, self.protocolclass.dunnoresponse) 108 109 return pbook
110
111 - def extractphonebookentry(self, slotnumber, entry, result):
112 """Return a phonebook entry in BitPim format. This is called from getphonebook.""" 113 res={} 114 # serials 115 res['serials']=[ {'sourcetype': self.serialsname, 'slot': slotnumber, 116 'sourceuniqueid': result['uniqueserial']} ] 117 # numbers 118 numbers=[] 119 for t, v in ( ('cell', entry.mobile), ('home', entry.home), ('office', entry.office), 120 ('pager', entry.pager), ('fax', entry.fax) ): 121 if len(v)==0: 122 continue 123 numbers.append( {'number': v, 'type': t} ) 124 if len(numbers): 125 res['numbers']=numbers 126 # name 127 if len(entry.name): # yes, the audiovox can have a blank name! 128 res['names']=[{'full': entry.name}] 129 # emails (we treat wireless as email addr) 130 emails=[] 131 if len(entry.email): 132 emails.append({'email': entry.email}) 133 if len(entry.wireless): 134 emails.append({'email': entry.wireless}) 135 if len(emails): 136 res['emails']=emails 137 # memo 138 if len(entry.memo): 139 res['memos']=[{'memo': entry.memo}] 140 # secret 141 if entry.secret: 142 res['flags']=[{'secret': True}] 143 # group 144 if entry.group in result['groups']: 145 group=result['groups'][entry.group]['name'] 146 if group!="Etc.": # this is the "bucket" group on the phone, which we treat as no group at all 147 res['categories']=[{'category': group}] 148 # media 149 rt=[] 150 if entry.ringtone!=0xffff: 151 rt.append({'ringtone': 'avox '+`entry.ringtone`, 'use': 'call'}) 152 if entry.msgringtone!=0xffff: 153 rt.append({'ringtone': 'avox '+`entry.msgringtone`, 'use': 'message'}) 154 if len(rt): 155 res['ringtones']=rt 156 if entry.wallpaper!=0xffff: 157 res['wallpapers']=[{'wallpaper': 'avox '+`entry.wallpaper`, 'use': 'call'}] 158 return res
159
160 - def makephonebookentry(self, fields):
161 e=self.protocolclass.pbentry() 162 # some defaults 163 e.secret=0 164 e.previous=0xffff 165 e.next=0xffff 166 e.ringtone=0xffff 167 e.msgringtone=0xffff 168 e.wallpaper=0xffff 169 for f in fields: 170 setattr(e, f, fields[f]) 171 if e.group==0: 172 raise Exception("Data error: The group cannot be zero or the phone crashes") 173 return e
174
175 - def savephonebook(self, data):
176 self.log("Saving group information") 177 178 for gid in range(1, self.protocolclass._NUMGROUPS): # do not save group 0 - All 179 name=data['groups'].get(gid, {'name': ''})['name'] 180 req=self.protocolclass.writegroupentryrequest() 181 req.number=gid 182 req.anothernumber=gid 183 req.name=name 184 req.nummembers=data['groups'].get(gid, {'members': 0})['members'] 185 self.log("Group #%d %s - %d members" % (gid, `name`, req.nummembers)) 186 self.sendpbcommand(req, self.protocolclass.writegroupentryresponse) 187 188 189 self.log("New phonebook\n"+common.prettyprintdict(data['phonebook'])) 190 191 # in theory we should offline the phone and wait two seconds first ... 192 193 pb=data['phonebook'] 194 keys=pb.keys() 195 keys.sort() 196 keys=keys[:self.protocolclass._NUMSLOTS] 197 # work out the bitmap. 198 slots=[] 199 for i in range(self.protocolclass._NUMSLOTS): 200 if i not in keys: 201 slots.append(0) 202 continue 203 bmp=0 204 e=pb[i] 205 if len(e['mobile']): bmp|=1 206 if len(e['home']): bmp|=2 207 if len(e['office']): bmp|=4 208 if len(e['pager']): bmp|=8 209 if len(e['fax']): bmp|=16 210 if len(e['email']): bmp|=32 211 if len(e['wireless']): bmp|=64 212 slots.append(bmp) 213 slots="".join([chr(x) for x in slots]) 214 req=self.protocolclass.writepbslotsrequest() 215 req.present=slots 216 self.sendpbcommand(req, self.protocolclass.writepbslotsresponse) 217 # now write out each slot 218 for i in range(len(keys)): 219 slot=keys[i] 220 req=self.protocolclass.writepbentryrequest() 221 req.slotnumber=slot 222 req.entry=self.makephonebookentry(pb[slot]) 223 self.log('Writing entry '+`slot`+" - "+req.entry.name) 224 self.progress(i, len(keys), "Writing "+req.entry.name) 225 self.sendpbcommand(req, self.protocolclass.writepbentryresponse) 226 self.progress(len(keys)+1, len(keys)+1, "Phone book write completed - phone will be rebooted") 227 data['rebootphone']=True 228 return data
229 230
231 - def sendpbcommand(self, request, responseclass):
232 self.setmode(self.MODEBREW) 233 buffer=prototypes.buffer() 234 request.writetobuffer(buffer, logtitle="audiovox cdm8900 phonebook request") 235 data=buffer.getvalue() 236 data=common.pppescape(data+common.crcs(data))+common.pppterminator 237 first=data[0] 238 try: 239 data=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False) 240 except com_phone.modeignoreerrortypes: 241 self.mode=self.MODENONE 242 self.raisecommsdnaexception("manipulating the phonebook") 243 self.comm.success=True 244 245 origdata=data 246 # sometimes there is junk at the begining, eg if the user 247 # turned off the phone and back on again. So if there is more 248 # than one 7e in the escaped data we should start after the 249 # second to last one 250 d=data.rfind(common.pppterminator,0,-1) 251 if d>=0: 252 self.log("Multiple PB packets in data - taking last one starting at "+`d+1`) 253 self.logdata("Original pb data", origdata, None) 254 data=data[d+1:] 255 256 # turn it back to normal 257 data=common.pppunescape(data) 258 259 # sometimes there is other crap at the begining 260 d=data.find(first) 261 if d>0: 262 self.log("Junk at begining of pb packet, data at "+`d`) 263 self.logdata("Original pb data", origdata, None) 264 self.logdata("Working on pb data", data, None) 265 data=data[d:] 266 # take off crc and terminator 267 crc=data[-3:-1] 268 data=data[:-3] 269 if common.crcs(data)!=crc: 270 self.logdata("Original pb data", origdata, None) 271 self.logdata("Working on pb data", data, None) 272 raise common.CommsDataCorruption("Audiovox phonebook packet failed CRC check", self.desc) 273 274 # parse data 275 buffer=prototypes.buffer(data) 276 res=responseclass() 277 res.readfrombuffer(buffer, logtitle="Audiovox phonebook response") 278 return res
279 280 281
282 -class Profile(com_phone.Profile):
283 284 protocolclass=Phone.protocolclass 285 serialsname=Phone.serialsname 286 287 WALLPAPER_WIDTH=128 288 WALLPAPER_HEIGHT=145 289 WALLPAPER_CONVERT_FORMAT="jpg" 290 291 MAX_WALLPAPER_BASENAME_LENGTH=16 292 WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ." 293 294 MAX_RINGTONE_BASENAME_LENGTH=16 295 RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ." 296 297 # which usb ids correspond to us 298 usbids=( (0x106c, 0x2101, 1), # VID=Curitel, PID=Audiovox CDM 8900, internal modem interface 299 ) 300 # which device classes we are. 301 deviceclasses=("modem",) 302 303 # what types of syncing we support 304 _supportedsyncs=( 305 ('phonebook', 'read', None), # all phonebook reading 306 ('phonebook', 'write', 'OVERWRITE'), # phonebook overwrite only 307 ) 308
309 - def _getgroup(self, name, groups):
310 for key in groups: 311 if groups[key]['name']==name: 312 return key,groups[key] 313 return None,None
314
315 - def normalisegroups(self, helper, data):
316 "Assigns groups based on category data" 317 318 keys=data['groups'].keys() 319 keys.sort() 320 pad=[data['groups'][k]['name'] for k in keys if k] # ignore key 0 - All 321 322 groups=helper.getmostpopularcategories(self.protocolclass._NUMGROUPS, data['phonebook'], ["All", "Business", "Personal", "Etc."], 323 self.protocolclass._MAXGROUPLEN, pad) 324 325 # alpha sort 326 groups.sort() 327 328 # newgroups 329 newgroups={} 330 331 # put in No group 332 newgroups[0]={'name': 'All', 'members': 0} 333 334 # populate 335 for name in groups: 336 # existing entries remain unchanged 337 if name=="All": continue 338 key,value=self._getgroup(name, data['groups']) 339 if key is not None and key!=0: 340 newgroups[key]=value 341 newgroups[key]['members']=0 342 343 # new entries get whatever numbers are free 344 for name in groups: 345 key,value=self._getgroup(name, newgroups) 346 if key is None: 347 for key in range(1,10000): 348 if key not in newgroups: 349 newgroups[key]={'name': name, 'members': 0} 350 break 351 # yay, done 352 data['groups']=newgroups
353
354 - def convertphonebooktophone(self, helper, data):
355 """Converts the data to what will be used by the phone 356 357 @param data: contains the dict returned by getfundamentals 358 as well as where the results go""" 359 self.normalisegroups(helper, data) 360 results={} 361 362 # find which entries are already known to this phone 363 pb=data['phonebook'] 364 # decorate list with (slot, pbkey) tuples 365 slots=[ (helper.getserial(pb[pbentry].get("serials", []), self.serialsname, data['uniqueserial'], "slot", None), pbentry) 366 for pbentry in pb] 367 slots.sort() # numeric order 368 # make two lists - one contains known slots, one doesn't 369 newones=[(pbentry,slot) for slot,pbentry in slots if slot is None] 370 existing=[(pbentry,slot) for slot,pbentry in slots if slot is not None] 371 372 for pbentry,slot in newones+existing: 373 if len(results)==self.protocolclass._NUMSLOTS: 374 break 375 try: 376 e={} # entry out 377 entry=data['phonebook'][pbentry] 378 e['mobile']=self.phonize(helper.getnumber(entry.get('numbers', []), 'cell')) 379 e['home']=self.phonize(helper.getnumber(entry.get('numbers', []), 'home')) 380 e['office']=self.phonize(helper.getnumber(entry.get('numbers', []), 'office')) 381 e['pager']=self.phonize(helper.getnumber(entry.get('numbers', []), 'pager')) 382 e['fax']=self.phonize(helper.getnumber(entry.get('numbers', []), 'fax')) 383 emails=helper.getemails(entry.get('emails', []), 0, 2, self.protocolclass._MAXEMAILLEN) 384 e['email']="" 385 e['wireless']="" 386 if len(emails)>=1: 387 e['email']=emails[0] 388 if len(emails)>=2: 389 e['wireless']=emails[1] 390 391 # must be at least one number or email 392 if max([len(e[field]) for field in e])==0: 393 # ::TODO:: log that we are ignoring this entry 394 continue 395 396 e['name']=helper.getfullname(entry.get('names', [ {'full': ''}]), 1, 1, self.protocolclass._MAXNAMELEN)[0] 397 e['memo']=helper.getmemos(entry.get('memos', [{'memo': ''}]), 1,1, self.protocolclass._MAXMEMOLEN)[0] 398 399 rt=helper.getringtone(entry.get('ringtones', []), 'call', "") 400 if rt.startswith("avox "): 401 e['ringtone']=int(rt[5:]) 402 403 rt=helper.getringtone(entry.get('ringtones', []), 'message', "") 404 if rt.startswith("avox "): 405 e['msgringtone']=int(rt[5:]) 406 407 wp=helper.getwallpaper(entry.get('wallpapers', []), 'call', "") 408 if wp.startswith("avox "): 409 e['wallpaper']=int(wp[5:]) 410 411 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False) 412 413 # deal with group 414 group=helper.getcategory(entry.get('categories', [{'category': 'Etc.'}]),1,1,self.protocolclass._MAXGROUPLEN)[0] 415 gid,_=self._getgroup(group, data['groups']) 416 if gid is None or gid==0: 417 gid,_=self._getgroup("Etc.", data['groups']) 418 assert gid!=0 419 e['group']=gid 420 data['groups'][gid]['members']+=1 421 # find the right slot 422 if slot is None or slot<0 or slot>=self.protocolclass._NUMSLOTS or slot in results: 423 for i in range(100000): 424 if i not in results: 425 slot=i 426 break 427 results[slot]=e 428 except helper.ConversionFailed: 429 continue 430 data['phonebook']=results 431 return data
432 433
434 - def phonize(self, str):
435 """Convert the phone number into something the phone understands 436 437 All digits, P, T, *, # are kept, everything else is removed. 438 In theory the phone can store a dash in the phonebook, but that 439 is not normal.""" 440 return re.sub("[^0-9PT#*]", "", str)[:self.protocolclass._MAXPHONENUMBERLEN]
441