PyXR

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



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