PyXR

c:\projects\bitpim\src \ phones \ com_audiovoxcdm8900.py



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